From 010a1351ea10039d39763a84b6d13df4fbf92666 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sat, 27 Apr 2019 19:21:02 +0200 Subject: [PATCH 01/14] Rename files & symbols --- .drone.yml | 16 +- .env.sample | 10 +- .realize.yaml | 6 +- Dockerfile.api-crm => Dockerfile.api-compose | 4 +- Makefile | 18 +- README.md | 6 +- api/{crm => compose}/spec.json | 16 +- api/{crm => compose}/spec/attachment.json | 0 api/{crm => compose}/spec/chart.json | 0 api/{crm => compose}/spec/module.json | 4 +- api/{crm => compose}/spec/notification.json | 4 +- api/{crm => compose}/spec/page.json | 2 +- api/{crm => compose}/spec/permissions.json | 0 api/{crm => compose}/spec/record.json | 4 +- api/{crm => compose}/spec/trigger.json | 2 +- cmd/{crm => compose}/main.go | 8 +- cmd/crust/main.go | 22 +- codegen.sh | 16 +- codegen/{crm => compose}/docs/index.php | 0 .../{crm => compose}/rest/handlers/index.php | 0 codegen/{crm => compose}/rest/index.php | 0 .../{crm => compose}/rest/request/index.php | 0 codegen/{crm => compose}/types/index.php | 0 {crm => compose}/db/migrate.go | 2 +- {crm => compose}/db/migrate_test.go | 0 {crm => compose}/db/mysql/static.go | 2 +- .../schema/mysql/20180704080000.base.up.sql | 0 .../20180704080001.crm_fields-data.up.sql | 0 ...0181109133134.crm_content-ownership.up.sql | 0 ...1109193047.crm_fields-related_types.up.sql | 0 ...25122152.add_multiple_relationships.up.sql | 0 ...d_and_visible_to_module_form_fields.up.sql | 0 ...130.fix-crm-module-form-primary-key.up.sql | 0 ...04123650.add-crm-content-json-field.up.sql | 0 ...5326.add-crm-module-form-json-field.up.sql | 0 ...0181216214630.crm-content-to-record.up.sql | 0 .../20181217100000.add-charts-tbl.up.sql | 0 .../mysql/20181224122301.rem-crm_field.up.sql | 0 .../20190108100000.add-triggers-tbl.up.sql | 0 ...110175924.rem-crm-record-json-field.up.sql | 0 ....cleanup-record-tables-and-multival.up.sql | 0 .../20190121132408.record-updated-by.up.sql | 0 .../mysql/20190227090642.attachment.up.sql | 0 .../20190427180922.change-tbl-prefix.up.sql | 32 + .../db/schema/mysql/migrations.sql | 0 {crm => compose}/db/schema/mysql/new.sh | 0 {crm => compose}/db/types.go | 0 {crm => compose}/flags.go | 2 +- .../internal/repository/attachment.go | 16 +- {crm => compose}/internal/repository/chart.go | 10 +- {crm => compose}/internal/repository/flags.go | 0 .../internal/repository/module.go | 16 +- {crm => compose}/internal/repository/page.go | 22 +- .../internal/repository/ql/README.md | 0 .../internal/repository/ql/ast_nodes.go | 0 .../internal/repository/ql/ast_parser.go | 0 .../internal/repository/ql/ast_parser_test.go | 0 .../internal/repository/ql/handlers.go | 0 .../internal/repository/ql/lexer.go | 0 .../internal/repository/ql/lexer_test.go | 0 .../internal/repository/ql/squirrel.go | 0 .../internal/repository/ql/token_codes.go | 0 .../internal/repository/ql/token_consumers.go | 0 .../internal/repository/record.go | 38 +- .../repository/record_report_builder.go | 8 +- .../repository/record_report_builder_test.go | 8 +- .../internal/repository/record_test.go | 2 +- .../internal/repository/repository.go | 2 +- .../internal/repository/repository_test.go | 0 .../internal/repository/sqirrel.go | 0 .../internal/repository/trigger.go | 10 +- .../internal/service/attachment.go | 4 +- {crm => compose}/internal/service/chart.go | 4 +- .../internal/service/chart_test.go | 0 .../internal/service/main_test.go | 8 +- {crm => compose}/internal/service/module.go | 4 +- .../internal/service/module_test.go | 2 +- .../internal/service/notification.go | 0 {crm => compose}/internal/service/page.go | 4 +- .../internal/service/page_test.go | 2 +- .../internal/service/permissions.go | 4 +- .../internal/service/permissions_test.go | 4 +- {crm => compose}/internal/service/record.go | 4 +- .../internal/service/record_test.go | 2 +- {crm => compose}/internal/service/service.go | 0 {crm => compose}/internal/service/trigger.go | 4 +- .../internal/service/trigger_test.go | 2 +- {crm => compose}/rest/attachment.go | 6 +- {crm => compose}/rest/chart.go | 6 +- {crm => compose}/rest/handlers/attachment.go | 2 +- {crm => compose}/rest/handlers/chart.go | 2 +- {crm => compose}/rest/handlers/module.go | 2 +- .../rest/handlers/notification.go | 2 +- {crm => compose}/rest/handlers/page.go | 2 +- {crm => compose}/rest/handlers/permissions.go | 2 +- {crm => compose}/rest/handlers/record.go | 2 +- {crm => compose}/rest/handlers/trigger.go | 2 +- {crm => compose}/rest/middleware.go | 2 +- {crm => compose}/rest/module.go | 6 +- {crm => compose}/rest/notification.go | 4 +- {crm => compose}/rest/page.go | 6 +- {crm => compose}/rest/permissions.go | 4 +- {crm => compose}/rest/record.go | 6 +- {crm => compose}/rest/request/attachment.go | 0 {crm => compose}/rest/request/chart.go | 0 {crm => compose}/rest/request/misc.go | 0 {crm => compose}/rest/request/module.go | 2 +- {crm => compose}/rest/request/notification.go | 0 {crm => compose}/rest/request/page.go | 0 {crm => compose}/rest/request/permissions.go | 0 {crm => compose}/rest/request/record.go | 2 +- {crm => compose}/rest/request/trigger.go | 0 {crm => compose}/rest/request/util.go | 0 {crm => compose}/rest/request/util_test.go | 0 {crm => compose}/rest/router.go | 2 +- {crm => compose}/rest/trigger.go | 6 +- {crm => compose}/routes.go | 2 +- {crm => compose}/start.go | 6 +- {crm => compose}/types/attachment.gen.go | 0 {crm => compose}/types/attachment.go | 0 {crm => compose}/types/chart.gen.go | 0 {crm => compose}/types/chart.go | 1 - {crm => compose}/types/module.gen.go | 0 {crm => compose}/types/module.go | 1 - {crm => compose}/types/module_field.gen.go | 0 {crm => compose}/types/module_field.go | 0 {crm => compose}/types/namespace.go | 0 {crm => compose}/types/page.gen.go | 0 {crm => compose}/types/page.go | 0 .../types/permission_resources.go | 0 {crm => compose}/types/record.gen.go | 0 {crm => compose}/types/record.go | 0 {crm => compose}/types/record_value.gen.go | 0 {crm => compose}/types/record_value.go | 0 {crm => compose}/types/trigger.gen.go | 0 {crm => compose}/types/trigger.go | 0 docs/crm/README.md | 693 ------------------ 137 files changed, 231 insertions(+), 896 deletions(-) rename Dockerfile.api-crm => Dockerfile.api-compose (87%) rename api/{crm => compose}/spec.json (98%) rename api/{crm => compose}/spec/attachment.json (100%) rename api/{crm => compose}/spec/chart.json (100%) rename api/{crm => compose}/spec/module.json (96%) rename api/{crm => compose}/spec/notification.json (93%) rename api/{crm => compose}/spec/page.json (99%) rename api/{crm => compose}/spec/permissions.json (100%) rename api/{crm => compose}/spec/record.json (98%) rename api/{crm => compose}/spec/trigger.json (98%) rename cmd/{crm => compose}/main.go (87%) rename codegen/{crm => compose}/docs/index.php (100%) rename codegen/{crm => compose}/rest/handlers/index.php (100%) rename codegen/{crm => compose}/rest/index.php (100%) rename codegen/{crm => compose}/rest/request/index.php (100%) rename codegen/{crm => compose}/types/index.php (100%) rename {crm => compose}/db/migrate.go (98%) rename {crm => compose}/db/migrate_test.go (100%) rename {crm => compose}/db/mysql/static.go (68%) rename {crm => compose}/db/schema/mysql/20180704080000.base.up.sql (100%) rename {crm => compose}/db/schema/mysql/20180704080001.crm_fields-data.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181109133134.crm_content-ownership.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181109193047.crm_fields-related_types.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181125122152.add_multiple_relationships.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181125132142.add_required_and_visible_to_module_form_fields.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181202163130.fix-crm-module-form-primary-key.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181204123650.add-crm-content-json-field.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181204155326.add-crm-module-form-json-field.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181216214630.crm-content-to-record.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181217100000.add-charts-tbl.up.sql (100%) rename {crm => compose}/db/schema/mysql/20181224122301.rem-crm_field.up.sql (100%) rename {crm => compose}/db/schema/mysql/20190108100000.add-triggers-tbl.up.sql (100%) rename {crm => compose}/db/schema/mysql/20190110175924.rem-crm-record-json-field.up.sql (100%) rename {crm => compose}/db/schema/mysql/20190114072000.cleanup-record-tables-and-multival.up.sql (100%) rename {crm => compose}/db/schema/mysql/20190121132408.record-updated-by.up.sql (100%) rename {crm => compose}/db/schema/mysql/20190227090642.attachment.up.sql (100%) create mode 100644 compose/db/schema/mysql/20190427180922.change-tbl-prefix.up.sql rename {crm => compose}/db/schema/mysql/migrations.sql (100%) rename {crm => compose}/db/schema/mysql/new.sh (100%) rename {crm => compose}/db/types.go (100%) rename {crm => compose}/flags.go (95%) rename {crm => compose}/internal/repository/attachment.go (86%) rename {crm => compose}/internal/repository/chart.go (83%) rename {crm => compose}/internal/repository/flags.go (100%) rename {crm => compose}/internal/repository/module.go (83%) rename {crm => compose}/internal/repository/page.go (69%) rename {crm => compose}/internal/repository/ql/README.md (100%) rename {crm => compose}/internal/repository/ql/ast_nodes.go (100%) rename {crm => compose}/internal/repository/ql/ast_parser.go (100%) rename {crm => compose}/internal/repository/ql/ast_parser_test.go (100%) rename {crm => compose}/internal/repository/ql/handlers.go (100%) rename {crm => compose}/internal/repository/ql/lexer.go (100%) rename {crm => compose}/internal/repository/ql/lexer_test.go (100%) rename {crm => compose}/internal/repository/ql/squirrel.go (100%) rename {crm => compose}/internal/repository/ql/token_codes.go (100%) rename {crm => compose}/internal/repository/ql/token_consumers.go (100%) rename {crm => compose}/internal/repository/record.go (83%) rename {crm => compose}/internal/repository/record_report_builder.go (94%) rename {crm => compose}/internal/repository/record_report_builder_test.go (71%) rename {crm => compose}/internal/repository/record_test.go (97%) rename {crm => compose}/internal/repository/repository.go (94%) rename {crm => compose}/internal/repository/repository_test.go (100%) rename {crm => compose}/internal/repository/sqirrel.go (100%) rename {crm => compose}/internal/repository/trigger.go (88%) rename {crm => compose}/internal/service/attachment.go (98%) rename {crm => compose}/internal/service/chart.go (96%) rename {crm => compose}/internal/service/chart_test.go (100%) rename {crm => compose}/internal/service/main_test.go (77%) rename {crm => compose}/internal/service/module.go (96%) rename {crm => compose}/internal/service/module_test.go (98%) rename {crm => compose}/internal/service/notification.go (100%) rename {crm => compose}/internal/service/page.go (97%) rename {crm => compose}/internal/service/page_test.go (99%) rename {crm => compose}/internal/service/permissions.go (98%) rename {crm => compose}/internal/service/permissions_test.go (95%) rename {crm => compose}/internal/service/record.go (98%) rename {crm => compose}/internal/service/record_test.go (99%) rename {crm => compose}/internal/service/service.go (100%) rename {crm => compose}/internal/service/trigger.go (96%) rename {crm => compose}/internal/service/trigger_test.go (98%) rename {crm => compose}/rest/attachment.go (96%) rename {crm => compose}/rest/chart.go (88%) rename {crm => compose}/rest/handlers/attachment.go (98%) rename {crm => compose}/rest/handlers/chart.go (98%) rename {crm => compose}/rest/handlers/module.go (98%) rename {crm => compose}/rest/handlers/notification.go (97%) rename {crm => compose}/rest/handlers/page.go (99%) rename {crm => compose}/rest/handlers/permissions.go (97%) rename {crm => compose}/rest/handlers/record.go (99%) rename {crm => compose}/rest/handlers/trigger.go (98%) rename {crm => compose}/rest/middleware.go (86%) rename {crm => compose}/rest/module.go (88%) rename {crm => compose}/rest/notification.go (92%) rename {crm => compose}/rest/page.go (94%) rename {crm => compose}/rest/permissions.go (80%) rename {crm => compose}/rest/record.go (93%) rename {crm => compose}/rest/request/attachment.go (100%) rename {crm => compose}/rest/request/chart.go (100%) rename {crm => compose}/rest/request/misc.go (100%) rename {crm => compose}/rest/request/module.go (99%) rename {crm => compose}/rest/request/notification.go (100%) rename {crm => compose}/rest/request/page.go (100%) rename {crm => compose}/rest/request/permissions.go (100%) rename {crm => compose}/rest/request/record.go (99%) rename {crm => compose}/rest/request/trigger.go (100%) rename {crm => compose}/rest/request/util.go (100%) rename {crm => compose}/rest/request/util_test.go (100%) rename {crm => compose}/rest/router.go (95%) rename {crm => compose}/rest/trigger.go (91%) rename {crm => compose}/routes.go (95%) rename {crm => compose}/start.go (90%) rename {crm => compose}/types/attachment.gen.go (100%) rename {crm => compose}/types/attachment.go (100%) rename {crm => compose}/types/chart.gen.go (100%) rename {crm => compose}/types/chart.go (94%) rename {crm => compose}/types/module.gen.go (100%) rename {crm => compose}/types/module.go (95%) rename {crm => compose}/types/module_field.gen.go (100%) rename {crm => compose}/types/module_field.go (100%) rename {crm => compose}/types/namespace.go (100%) rename {crm => compose}/types/page.gen.go (100%) rename {crm => compose}/types/page.go (100%) rename {crm => compose}/types/permission_resources.go (100%) rename {crm => compose}/types/record.gen.go (100%) rename {crm => compose}/types/record.go (100%) rename {crm => compose}/types/record_value.gen.go (100%) rename {crm => compose}/types/record_value.go (100%) rename {crm => compose}/types/trigger.gen.go (100%) rename {crm => compose}/types/trigger.go (100%) delete mode 100644 docs/crm/README.md diff --git a/.drone.yml b/.drone.yml index 8913a10c1..917de60f5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,7 +15,7 @@ steps: # GOARCH: amd64 # commands: # - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/crust-$GOOS-$GOARCH cmd/crust/*.go -# - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/crm-$GOOS-$GOARCH cmd/crm/*.go +# - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/compose-$GOOS-$GOARCH cmd/compose/*.go # - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/messaging-$GOOS-$GOARCH cmd/messaging/*.go # - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/system-$GOOS-$GOARCH cmd/system/*.go # - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/system-cli-$GOOS-$GOARCH cmd/system-cli/*.go @@ -32,12 +32,12 @@ steps: from_secret: docker_hub_password -- name: api-crm +- name: api-compose image: plugins/docker settings: - repo: crusttech/api-crm + repo: crusttech/api-compose auto_tag: true # generate tag names automatically based on git branch and git tag - dockerfile: Dockerfile.api-crm + dockerfile: Dockerfile.api-compose username: from_secret: docker_hub_username password: @@ -97,17 +97,17 @@ steps: - make mocks - make codegen - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/crust-$GOOS-$GOARCH cmd/crust/*.go - - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/crm-$GOOS-$GOARCH cmd/crm/*.go + - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/compose-$GOOS-$GOARCH cmd/compose/*.go - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/messaging-$GOOS-$GOARCH cmd/messaging/*.go - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/system-$GOOS-$GOARCH cmd/system/*.go - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/system-cli-$GOOS-$GOARCH cmd/system-cli/*.go # check all tests are able to build - wait-for-it.sh -t 60 --strict crust-db:3306 -- echo "Crust DB1 is up" - gotest -v --tags="migrations" ./system/db/... - - gotest -v --tags="migrations" ./crm/db/... + - gotest -v --tags="migrations" ./compose/db/... - gotest -v --tags="migrations" ./messaging/db/... - - go test -run=^$ --tags="unit integration external" ./cmd/... ./internal/... ./crm/... ./messaging/... ./system/... - - gotest -failfast --coverprofile=coverage.txt -v --tags="unit integration" ./internal/... ./system/... ./crm/... ./messaging/... + - go test -run=^$ --tags="unit integration external" ./cmd/... ./internal/... ./compose/... ./messaging/... ./system/... + - gotest -failfast --coverprofile=coverage.txt -v --tags="unit integration" ./internal/... ./system/... ./compose/... ./messaging/... - name: coverage image: plugins/codecov settings: diff --git a/.env.sample b/.env.sample index 540c702f5..d3a7367f2 100644 --- a/.env.sample +++ b/.env.sample @@ -4,11 +4,11 @@ MESSAGING_HTTP_ERROR_TRACING=1 MESSAGING_DB_DSN=crust:crust@tcp(localhost:3306)/crust?collation=utf8mb4_general_ci MESSAGING_DB_PROFILER=stdout -CRM_HTTP_ADDR=:3001 -CRM_HTTP_PRETTY_JSON=1 -CRM_HTTP_ERROR_TRACING=1 -CRM_DB_DSN=crust:crust@tcp(localhost:3306)/crust?collation=utf8mb4_general_ci -CRM_DB_PROFILER=stdout +COMPOSE_HTTP_ADDR=:3001 +COMPOSE_HTTP_PRETTY_JSON=1 +COMPOSE_HTTP_ERROR_TRACING=1 +COMPOSE_DB_DSN=crust:crust@tcp(localhost:3306)/crust?collation=utf8mb4_general_ci +COMPOSE_DB_PROFILER=stdout SYSTEM_HTTP_ADDR=:3002 SYSTEM_HTTP_PRETTY_JSON=1 diff --git a/.realize.yaml b/.realize.yaml index 8540df6cc..0c160ebc8 100755 --- a/.realize.yaml +++ b/.realize.yaml @@ -66,8 +66,8 @@ schema: - ../../.git # ###################################################################################################################### -- name: crm - path: cmd/crm +- name: cmp + path: cmd/compose commands: vet: status: false @@ -80,7 +80,7 @@ schema: watcher: paths: - . - - ../../crm + - ../../compose - ../../internal extensions: - go diff --git a/Dockerfile.api-crm b/Dockerfile.api-compose similarity index 87% rename from Dockerfile.api-crm rename to Dockerfile.api-compose index 30fb999df..cfc5bea92 100644 --- a/Dockerfile.api-crm +++ b/Dockerfile.api-compose @@ -8,7 +8,7 @@ COPY . . RUN mkdir /build; \ go build \ -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime="`date +%FT%T%z`" -X github.com/crusttech/crust/internal/version.Version="`git describe --always --tags` \ - -o /build/crm cmd/crm/*.go + -o /build/compose cmd/compose/*.go ## == target image == @@ -23,4 +23,4 @@ WORKDIR /crust EXPOSE 80 -ENTRYPOINT /crust/crm +ENTRYPOINT /crust/compose diff --git a/Makefile b/Makefile index 40ce13dac..efc1596d9 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ -.PHONY: help docker docker-push realize dep dep.update test test.messaging test.crm qa critic vet codegen integration +.PHONY: help docker docker-push realize dep dep.update test test.messaging test.compose qa critic vet codegen integration PKG = "github.com/$(shell cat .project)" GO = go GOGET = $(GO) get -u -BASEPKGS = system crm messaging -IMAGES = system crm messaging +BASEPKGS = system compose messaging +IMAGES = system compose messaging ######################################################################################################################## # Tool bins @@ -25,7 +25,7 @@ help: @echo @echo - vet - run go vet on all code @echo - critic - run go critic on all code - @echo - test.crm - individual package unit tests + @echo - test.compose - individual package unit tests @echo - test.messaging - individual package unit tests @echo - test - run all available unit tests @echo - qa - run vet, critic and test on code @@ -86,13 +86,13 @@ test.events: $(GOTEST) perl -pi -e 's/command-line-arguments/.\/messaging\/repository/g' .cover.out $(GO) tool cover -func=.cover.out | grep --color "^\|[^0-9]0.0%" -test.crm: $(GOTEST) - $(GOTEST) -covermode count -coverprofile .cover.out -v ./crm/service/... +test.compose: $(GOTEST) + $(GOTEST) -covermode count -coverprofile .cover.out -v ./compose/service/... $(GO) tool cover -func=.cover.out | grep --color "^\|[^0-9]0.0%" -test.crm.db: $(GOTEST) - cd crm/db && $(GO) generate && cd ../.. - $(GOTEST) -covermode count -coverprofile .cover.out -v ./crm/db/... +test.compose.db: $(GOTEST) + cd compose/db && $(GO) generate && cd ../.. + $(GOTEST) -covermode count -coverprofile .cover.out -v ./compose/db/... $(GO) tool cover -func=.cover.out | grep --color "^\|[^0-9]0.0%" test.system: $(GOTEST) diff --git a/README.md b/README.md index db270a9f7..7a174aa86 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Crust brings your user ecosystem and essential applications together on one plat **Crust CRM** is the highly flexible, scalable and open source Salesforce alternative, that enables you to sell faster and interact with leads, clients and team members easier then ever before. Seamless integration with Crust Messaging and Crust Identity and Access Management make it the most complete and flexible self-hosted CRM platform on the market. -**Crust Unify** manages user experience for Crust applications, such as CRM and Messaging, as well as providing an integrated interface for third party or other bespoke applications. 100% responsive and with an intuitive design, Crust Unify increases productivity and ease of access to all IT resources. +**Crust Unify** manages user experience for Crust applications, such as Compose and Messaging, as well as providing an integrated interface for third party or other bespoke applications. 100% responsive and with an intuitive design, Crust Unify increases productivity and ease of access to all IT resources. ## Contributing @@ -18,9 +18,7 @@ Crust brings your user ecosystem and essential applications together on one plat Copy `.env.example` to `.env` and make proper modifications for your local environment. An access to a (local) instance of MySQL must be available. -Configure access to your database with `SAM_DB_DSN` and `CRM_DB_DSN`. - -Please check the options available with `./app -h`. +Configure access to your database with `SYSTEM_DB_DSN`, `MESSAGING_DB_DSN` and `COMPOSE_DB_DSN`. The database will be populated with migrations at the start of each service. You don't need to pre-populate the database, just make sure that your permissions include CREATE and ALTER capabilities. diff --git a/api/crm/spec.json b/api/compose/spec.json similarity index 98% rename from api/crm/spec.json rename to api/compose/spec.json index f773d02b4..0fc5c11eb 100644 --- a/api/crm/spec.json +++ b/api/compose/spec.json @@ -26,7 +26,7 @@ }, { "title": "Pages", - "description": "CRM module pages", + "description": "Compose module pages", "entrypoint": "page", "path": "/page", "authentication": [], @@ -245,7 +245,7 @@ }, { "title": "Modules", - "description": "CRM module definitions", + "description": "Compose module definitions", "entrypoint": "module", "path": "/module", "authentication": [], @@ -253,7 +253,7 @@ { "imports": [ "sqlxTypes github.com/jmoiron/sqlx/types", - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ] } ], @@ -374,14 +374,14 @@ }, { "title": "Records", - "description": "CRM records ", + "description": "Compose records ", "entrypoint": "record", "path": "/module/{moduleID}/record", "authentication": [], "struct": [ { "imports": [ - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ] } ], @@ -669,7 +669,7 @@ }, { "title": "Triggers", - "description": "CRM Triggers", + "description": "Compose Triggers", "entrypoint": "trigger", "path": "/trigger", "authentication": [], @@ -814,7 +814,7 @@ }, { "title": "Notifications", - "description": "CRM Notifications", + "description": "Compose Notifications", "entrypoint": "notification", "path": "/notification", "authentication": [], @@ -829,7 +829,7 @@ { "name": "email/send", "method": "POST", - "title": "Send email from the CRM", + "title": "Send email from the Compose", "path": "/email", "parameters": { "post": [ diff --git a/api/crm/spec/attachment.json b/api/compose/spec/attachment.json similarity index 100% rename from api/crm/spec/attachment.json rename to api/compose/spec/attachment.json diff --git a/api/crm/spec/chart.json b/api/compose/spec/chart.json similarity index 100% rename from api/crm/spec/chart.json rename to api/compose/spec/chart.json diff --git a/api/crm/spec/module.json b/api/compose/spec/module.json similarity index 96% rename from api/crm/spec/module.json rename to api/compose/spec/module.json index 4d55ad861..9aa371166 100644 --- a/api/crm/spec/module.json +++ b/api/compose/spec/module.json @@ -1,12 +1,12 @@ { "Title": "Modules", - "Description": "CRM module definitions", + "Description": "Compose module definitions", "Interface": "Module", "Struct": [ { "imports": [ "sqlxTypes github.com/jmoiron/sqlx/types", - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ] } ], diff --git a/api/crm/spec/notification.json b/api/compose/spec/notification.json similarity index 93% rename from api/crm/spec/notification.json rename to api/compose/spec/notification.json index c1eed0a98..121e7c78b 100644 --- a/api/crm/spec/notification.json +++ b/api/compose/spec/notification.json @@ -1,6 +1,6 @@ { "Title": "Notifications", - "Description": "CRM Notifications", + "Description": "Compose Notifications", "Interface": "Notification", "Struct": [ { @@ -17,7 +17,7 @@ { "Name": "email/send", "Method": "POST", - "Title": "Send email from the CRM", + "Title": "Send email from the Compose", "Path": "/email", "Parameters": { "post": [ diff --git a/api/crm/spec/page.json b/api/compose/spec/page.json similarity index 99% rename from api/crm/spec/page.json rename to api/compose/spec/page.json index 0d50d05c9..54a9093d7 100644 --- a/api/crm/spec/page.json +++ b/api/compose/spec/page.json @@ -1,6 +1,6 @@ { "Title": "Pages", - "Description": "CRM module pages", + "Description": "Compose module pages", "Interface": "Page", "Struct": [ { diff --git a/api/crm/spec/permissions.json b/api/compose/spec/permissions.json similarity index 100% rename from api/crm/spec/permissions.json rename to api/compose/spec/permissions.json diff --git a/api/crm/spec/record.json b/api/compose/spec/record.json similarity index 98% rename from api/crm/spec/record.json rename to api/compose/spec/record.json index 9a3f244ec..a45d56dc3 100644 --- a/api/crm/spec/record.json +++ b/api/compose/spec/record.json @@ -1,11 +1,11 @@ { "Title": "Records", - "Description": "CRM records ", + "Description": "Compose records", "Interface": "Record", "Struct": [ { "imports": [ - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ] } ], diff --git a/api/crm/spec/trigger.json b/api/compose/spec/trigger.json similarity index 98% rename from api/crm/spec/trigger.json rename to api/compose/spec/trigger.json index 5d497cc60..0c6a6af2c 100644 --- a/api/crm/spec/trigger.json +++ b/api/compose/spec/trigger.json @@ -1,6 +1,6 @@ { "Title": "Triggers", - "Description": "CRM Triggers", + "Description": "Compose Triggers", "Interface": "Trigger", "Struct": null, "Parameters": null, diff --git a/cmd/crm/main.go b/cmd/compose/main.go similarity index 87% rename from cmd/crm/main.go rename to cmd/compose/main.go index ecd9e0d88..6663ae52a 100644 --- a/cmd/crm/main.go +++ b/cmd/compose/main.go @@ -12,7 +12,7 @@ import ( "github.com/crusttech/crust/internal/subscription" "github.com/crusttech/crust/internal/version" - crm "github.com/crusttech/crust/crm" + compose "github.com/crusttech/crust/compose" system "github.com/crusttech/crust/system" ) @@ -24,7 +24,7 @@ func main() { ctx := context.AsContext(sigctx.New()) - crm.Flags("crm") + compose.Flags("compose") system.Flags("system") subscription.Flags() @@ -34,7 +34,7 @@ func main() { if err := system.Init(ctx); err != nil { log.Fatalf("Error initializing: %+v", err) } - if err := crm.Init(ctx); err != nil { + if err := compose.Init(ctx); err != nil { log.Fatalf("Error initializing: %+v", err) } @@ -51,7 +51,7 @@ func main() { // Disabled for now, system service is the only one that validates subscription // ctx = subscription.Monitor(ctx) - if err := crm.StartRestAPI(ctx); err != nil { + if err := compose.StartRestAPI(ctx); err != nil { log.Fatalf("Error starting/running: %+v", err) } } diff --git a/cmd/crust/main.go b/cmd/crust/main.go index 5b5ea1c70..aeb3f61e2 100644 --- a/cmd/crust/main.go +++ b/cmd/crust/main.go @@ -13,7 +13,7 @@ import ( _ "github.com/joho/godotenv/autoload" "github.com/namsral/flag" - crm "github.com/crusttech/crust/crm" + compose "github.com/crusttech/crust/compose" "github.com/crusttech/crust/internal/auth" messaging "github.com/crusttech/crust/messaging" system "github.com/crusttech/crust/system" @@ -55,7 +55,7 @@ func main() { flags.http = new(config.HTTP).Init() flags.monitor = new(config.Monitor).Init() - crm.Flags("crm") + compose.Flags("compose") messaging.Flags("messaging") system.Flags("system") @@ -78,8 +78,8 @@ func main() { if err := system.Init(ctx); err != nil { log.Fatalf("Error initializing system: %+v", err) } - if err := crm.Init(ctx); err != nil { - log.Fatalf("Error initializing crm: %+v", err) + if err := compose.Init(ctx); err != nil { + log.Fatalf("Error initializing compose: %+v", err) } if err := messaging.Init(ctx); err != nil { log.Fatalf("Error initializing messaging: %+v", err) @@ -103,16 +103,16 @@ func main() { // logging, cors and such middleware.Mount(ctx, r, flags.http) - // Use JWT secret for hmac signer for now - auth.DefaultSigner = auth.HmacSigner(authJwtFlags.Secret) - auth.DefaultJwtHandler, err = auth.JWT(authJwtFlags.Secret, authJwtFlags.Expiry) + // Use JWT secret for hmac signer for now + auth.DefaultSigner = auth.HmacSigner(authJwtFlags.Secret) + auth.DefaultJwtHandler, err = auth.JWT(authJwtFlags.Secret, authJwtFlags.Expiry) if err != nil { log.Fatalf("Error creating JWT Auth: %v", err) } r.Route("/api", func(r chi.Router) { - r.Route("/crm", func(r chi.Router) { - crm.MountRoutes(ctx, r) + r.Route("/compose", func(r chi.Router) { + compose.MountRoutes(ctx, r) }) r.Route("/messaging", func(r chi.Router) { messaging.MountRoutes(ctx, r) @@ -125,8 +125,8 @@ func main() { fileserver := http.FileServer(http.Dir("webapp")) - for _, service := range []string{"admin", "system", "messaging", "crm"} { - r.HandleFunc("/"+service+"*", serveIndex("webapp", "crm/index.html", fileserver)) + for _, service := range []string{"admin", "system", "messaging", "compose"} { + r.HandleFunc("/"+service+"*", serveIndex("webapp", "compose/index.html", fileserver)) } r.HandleFunc("/*", serveIndex("webapp", "index.html", fileserver)) diff --git a/codegen.sh b/codegen.sh index 7bb557a5a..c03df6a4f 100755 --- a/codegen.sh +++ b/codegen.sh @@ -22,15 +22,15 @@ function types { CGO_ENABLED=0 go build -o ./build/gen-type-set codegen/v2/type-set.go fi - ./build/gen-type-set --types Attachment --output crm/types/attachment.gen.go - ./build/gen-type-set --types Module --output crm/types/module.gen.go - ./build/gen-type-set --types Page --output crm/types/page.gen.go - ./build/gen-type-set --types Chart --output crm/types/chart.gen.go - ./build/gen-type-set --types Trigger --output crm/types/trigger.gen.go - ./build/gen-type-set --types Record --output crm/types/record.gen.go + ./build/gen-type-set --types Attachment --output compose/types/attachment.gen.go + ./build/gen-type-set --types Module --output compose/types/module.gen.go + ./build/gen-type-set --types Page --output compose/types/page.gen.go + ./build/gen-type-set --types Chart --output compose/types/chart.gen.go + ./build/gen-type-set --types Trigger --output compose/types/trigger.gen.go + ./build/gen-type-set --types Record --output compose/types/record.gen.go - ./build/gen-type-set --with-primary-key=false --types ModuleField --output crm/types/module_field.gen.go - ./build/gen-type-set --with-primary-key=false --types RecordValue --output crm/types/record_value.gen.go + ./build/gen-type-set --with-primary-key=false --types ModuleField --output compose/types/module_field.gen.go + ./build/gen-type-set --with-primary-key=false --types RecordValue --output compose/types/record_value.gen.go ./build/gen-type-set --types MessageAttachment --output messaging/types/attachment.gen.go ./build/gen-type-set --types Mention --output messaging/types/mention.gen.go diff --git a/codegen/crm/docs/index.php b/codegen/compose/docs/index.php similarity index 100% rename from codegen/crm/docs/index.php rename to codegen/compose/docs/index.php diff --git a/codegen/crm/rest/handlers/index.php b/codegen/compose/rest/handlers/index.php similarity index 100% rename from codegen/crm/rest/handlers/index.php rename to codegen/compose/rest/handlers/index.php diff --git a/codegen/crm/rest/index.php b/codegen/compose/rest/index.php similarity index 100% rename from codegen/crm/rest/index.php rename to codegen/compose/rest/index.php diff --git a/codegen/crm/rest/request/index.php b/codegen/compose/rest/request/index.php similarity index 100% rename from codegen/crm/rest/request/index.php rename to codegen/compose/rest/request/index.php diff --git a/codegen/crm/types/index.php b/codegen/compose/types/index.php similarity index 100% rename from codegen/crm/types/index.php rename to codegen/compose/types/index.php diff --git a/crm/db/migrate.go b/compose/db/migrate.go similarity index 98% rename from crm/db/migrate.go rename to compose/db/migrate.go index 3c8258589..57eb8b12d 100644 --- a/crm/db/migrate.go +++ b/compose/db/migrate.go @@ -13,7 +13,7 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/db/mysql" + "github.com/crusttech/crust/compose/db/mysql" ) func statements(contents []byte, err error) ([]string, error) { diff --git a/crm/db/migrate_test.go b/compose/db/migrate_test.go similarity index 100% rename from crm/db/migrate_test.go rename to compose/db/migrate_test.go diff --git a/crm/db/mysql/static.go b/compose/db/mysql/static.go similarity index 68% rename from crm/db/mysql/static.go rename to compose/db/mysql/static.go index a7827c564..fc0c28726 100644 --- a/crm/db/mysql/static.go +++ b/compose/db/mysql/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package mysql -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81>(\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x13\x00\x13\x00\x14\x07\x00\x00\xaa(\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_content\n RENAME TO compose_content;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x8a)\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81G+\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x14\x00\x14\x00r\x07\x00\x00\xb3+\x00\x00\x00\x00" diff --git a/crm/db/schema/mysql/20180704080000.base.up.sql b/compose/db/schema/mysql/20180704080000.base.up.sql similarity index 100% rename from crm/db/schema/mysql/20180704080000.base.up.sql rename to compose/db/schema/mysql/20180704080000.base.up.sql diff --git a/crm/db/schema/mysql/20180704080001.crm_fields-data.up.sql b/compose/db/schema/mysql/20180704080001.crm_fields-data.up.sql similarity index 100% rename from crm/db/schema/mysql/20180704080001.crm_fields-data.up.sql rename to compose/db/schema/mysql/20180704080001.crm_fields-data.up.sql diff --git a/crm/db/schema/mysql/20181109133134.crm_content-ownership.up.sql b/compose/db/schema/mysql/20181109133134.crm_content-ownership.up.sql similarity index 100% rename from crm/db/schema/mysql/20181109133134.crm_content-ownership.up.sql rename to compose/db/schema/mysql/20181109133134.crm_content-ownership.up.sql diff --git a/crm/db/schema/mysql/20181109193047.crm_fields-related_types.up.sql b/compose/db/schema/mysql/20181109193047.crm_fields-related_types.up.sql similarity index 100% rename from crm/db/schema/mysql/20181109193047.crm_fields-related_types.up.sql rename to compose/db/schema/mysql/20181109193047.crm_fields-related_types.up.sql diff --git a/crm/db/schema/mysql/20181125122152.add_multiple_relationships.up.sql b/compose/db/schema/mysql/20181125122152.add_multiple_relationships.up.sql similarity index 100% rename from crm/db/schema/mysql/20181125122152.add_multiple_relationships.up.sql rename to compose/db/schema/mysql/20181125122152.add_multiple_relationships.up.sql diff --git a/crm/db/schema/mysql/20181125132142.add_required_and_visible_to_module_form_fields.up.sql b/compose/db/schema/mysql/20181125132142.add_required_and_visible_to_module_form_fields.up.sql similarity index 100% rename from crm/db/schema/mysql/20181125132142.add_required_and_visible_to_module_form_fields.up.sql rename to compose/db/schema/mysql/20181125132142.add_required_and_visible_to_module_form_fields.up.sql diff --git a/crm/db/schema/mysql/20181202163130.fix-crm-module-form-primary-key.up.sql b/compose/db/schema/mysql/20181202163130.fix-crm-module-form-primary-key.up.sql similarity index 100% rename from crm/db/schema/mysql/20181202163130.fix-crm-module-form-primary-key.up.sql rename to compose/db/schema/mysql/20181202163130.fix-crm-module-form-primary-key.up.sql diff --git a/crm/db/schema/mysql/20181204123650.add-crm-content-json-field.up.sql b/compose/db/schema/mysql/20181204123650.add-crm-content-json-field.up.sql similarity index 100% rename from crm/db/schema/mysql/20181204123650.add-crm-content-json-field.up.sql rename to compose/db/schema/mysql/20181204123650.add-crm-content-json-field.up.sql diff --git a/crm/db/schema/mysql/20181204155326.add-crm-module-form-json-field.up.sql b/compose/db/schema/mysql/20181204155326.add-crm-module-form-json-field.up.sql similarity index 100% rename from crm/db/schema/mysql/20181204155326.add-crm-module-form-json-field.up.sql rename to compose/db/schema/mysql/20181204155326.add-crm-module-form-json-field.up.sql diff --git a/crm/db/schema/mysql/20181216214630.crm-content-to-record.up.sql b/compose/db/schema/mysql/20181216214630.crm-content-to-record.up.sql similarity index 100% rename from crm/db/schema/mysql/20181216214630.crm-content-to-record.up.sql rename to compose/db/schema/mysql/20181216214630.crm-content-to-record.up.sql diff --git a/crm/db/schema/mysql/20181217100000.add-charts-tbl.up.sql b/compose/db/schema/mysql/20181217100000.add-charts-tbl.up.sql similarity index 100% rename from crm/db/schema/mysql/20181217100000.add-charts-tbl.up.sql rename to compose/db/schema/mysql/20181217100000.add-charts-tbl.up.sql diff --git a/crm/db/schema/mysql/20181224122301.rem-crm_field.up.sql b/compose/db/schema/mysql/20181224122301.rem-crm_field.up.sql similarity index 100% rename from crm/db/schema/mysql/20181224122301.rem-crm_field.up.sql rename to compose/db/schema/mysql/20181224122301.rem-crm_field.up.sql diff --git a/crm/db/schema/mysql/20190108100000.add-triggers-tbl.up.sql b/compose/db/schema/mysql/20190108100000.add-triggers-tbl.up.sql similarity index 100% rename from crm/db/schema/mysql/20190108100000.add-triggers-tbl.up.sql rename to compose/db/schema/mysql/20190108100000.add-triggers-tbl.up.sql diff --git a/crm/db/schema/mysql/20190110175924.rem-crm-record-json-field.up.sql b/compose/db/schema/mysql/20190110175924.rem-crm-record-json-field.up.sql similarity index 100% rename from crm/db/schema/mysql/20190110175924.rem-crm-record-json-field.up.sql rename to compose/db/schema/mysql/20190110175924.rem-crm-record-json-field.up.sql diff --git a/crm/db/schema/mysql/20190114072000.cleanup-record-tables-and-multival.up.sql b/compose/db/schema/mysql/20190114072000.cleanup-record-tables-and-multival.up.sql similarity index 100% rename from crm/db/schema/mysql/20190114072000.cleanup-record-tables-and-multival.up.sql rename to compose/db/schema/mysql/20190114072000.cleanup-record-tables-and-multival.up.sql diff --git a/crm/db/schema/mysql/20190121132408.record-updated-by.up.sql b/compose/db/schema/mysql/20190121132408.record-updated-by.up.sql similarity index 100% rename from crm/db/schema/mysql/20190121132408.record-updated-by.up.sql rename to compose/db/schema/mysql/20190121132408.record-updated-by.up.sql diff --git a/crm/db/schema/mysql/20190227090642.attachment.up.sql b/compose/db/schema/mysql/20190227090642.attachment.up.sql similarity index 100% rename from crm/db/schema/mysql/20190227090642.attachment.up.sql rename to compose/db/schema/mysql/20190227090642.attachment.up.sql diff --git a/compose/db/schema/mysql/20190427180922.change-tbl-prefix.up.sql b/compose/db/schema/mysql/20190427180922.change-tbl-prefix.up.sql new file mode 100644 index 000000000..dfb25b246 --- /dev/null +++ b/compose/db/schema/mysql/20190427180922.change-tbl-prefix.up.sql @@ -0,0 +1,32 @@ +DROP TABLE IF EXISTS crm_field; +DROP TABLE IF EXISTS crm_fields; +DROP TABLE IF EXISTS crm_content_links; +DROP TABLE IF EXISTS crm_content_column; +DROP TABLE IF EXISTS crm_module_content; + +ALTER TABLE crm_attachment + RENAME TO compose_attachment; + +ALTER TABLE crm_chart + RENAME TO compose_chart; + +ALTER TABLE crm_content + RENAME TO compose_content; + +ALTER TABLE crm_module + RENAME TO compose_module; + +ALTER TABLE crm_module_form + RENAME TO compose_module_form; + +ALTER TABLE crm_page + RENAME TO compose_page; + +ALTER TABLE crm_record + RENAME TO compose_record; + +ALTER TABLE crm_record_value + RENAME TO compose_record_value; + +ALTER TABLE crm_trigger + RENAME TO compose_trigger; diff --git a/crm/db/schema/mysql/migrations.sql b/compose/db/schema/mysql/migrations.sql similarity index 100% rename from crm/db/schema/mysql/migrations.sql rename to compose/db/schema/mysql/migrations.sql diff --git a/crm/db/schema/mysql/new.sh b/compose/db/schema/mysql/new.sh similarity index 100% rename from crm/db/schema/mysql/new.sh rename to compose/db/schema/mysql/new.sh diff --git a/crm/db/types.go b/compose/db/types.go similarity index 100% rename from crm/db/types.go rename to compose/db/types.go diff --git a/crm/flags.go b/compose/flags.go similarity index 95% rename from crm/flags.go rename to compose/flags.go index 1621055eb..4c616d35f 100644 --- a/crm/flags.go +++ b/compose/flags.go @@ -3,7 +3,7 @@ package service import ( "github.com/pkg/errors" - "github.com/crusttech/crust/crm/internal/repository" + "github.com/crusttech/crust/compose/internal/repository" "github.com/crusttech/crust/internal/config" ) diff --git a/crm/internal/repository/attachment.go b/compose/internal/repository/attachment.go similarity index 86% rename from crm/internal/repository/attachment.go rename to compose/internal/repository/attachment.go index 1443ef500..373b20c81 100644 --- a/crm/internal/repository/attachment.go +++ b/compose/internal/repository/attachment.go @@ -9,7 +9,7 @@ import ( "github.com/titpetric/factory" sq "gopkg.in/Masterminds/squirrel.v1" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ) type ( @@ -39,10 +39,10 @@ const ( sqlAttachmentScope = "deleted_at IS NULL" sqlAttachmentByID = `SELECT ` + sqlAttachmentColumns + - ` FROM crm_attachment AS a WHERE id = ? AND ` + sqlAttachmentScope + ` FROM compose_attachment AS a WHERE id = ? AND ` + sqlAttachmentScope sqlAttachmentsByIDs = `SELECT ` + sqlAttachmentColumns + - ` FROM crm_attachment AS a WHERE id IN (?) AND ` + sqlAttachmentScope + ` FROM compose_attachment AS a WHERE id IN (?) AND ` + sqlAttachmentScope ) func Attachment(ctx context.Context, db *factory.DB) AttachmentRepository { @@ -85,7 +85,7 @@ func (r *attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSe set = types.AttachmentSet{} - query := sq.Select().From("crm_attachment AS a").Where(sq.Eq{"a.kind": f.Kind}) + query := sq.Select().From("compose_attachment AS a").Where(sq.Eq{"a.kind": f.Kind}) switch f.Kind { case types.PageAttachment: @@ -96,11 +96,11 @@ func (r *attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSe } case types.RecordAttachment: query = query. - Join("crm_record_value AS v ON (v.ref = a.id)") + Join("compose_record_value AS v ON (v.ref = a.id)") if f.ModuleID > 0 { query = query. - Join("crm_record AS r ON (r.id = v.record_id)"). + Join("compose_record AS r ON (r.id = v.record_id)"). Where(sq.Eq{"r.module_id": f.ModuleID}) } @@ -156,10 +156,10 @@ func (r *attachment) Create(mod *types.Attachment) (*types.Attachment, error) { mod.CreatedAt = time.Now() - return mod, r.db().Insert("crm_attachment", mod) + return mod, r.db().Insert("compose_attachment", mod) } func (r *attachment) DeleteByID(id uint64) error { - _, err := r.db().Exec("UPDATE crm_attachment SET deleted_at = NOW() WHERE id = ?", id) + _, err := r.db().Exec("UPDATE compose_attachment SET deleted_at = NOW() WHERE id = ?", id) return err } diff --git a/crm/internal/repository/chart.go b/compose/internal/repository/chart.go similarity index 83% rename from crm/internal/repository/chart.go rename to compose/internal/repository/chart.go index b650900b6..9bd1c0db8 100644 --- a/crm/internal/repository/chart.go +++ b/compose/internal/repository/chart.go @@ -6,7 +6,7 @@ import ( "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ) type ( @@ -28,7 +28,7 @@ type ( const sqlChartColumns = "id, name, config, " + "created_at, updated_at, deleted_at" -const sqlChartSelect = "SELECT " + sqlChartColumns + " FROM crm_chart" +const sqlChartSelect = "SELECT " + sqlChartColumns + " FROM compose_chart" func Chart(ctx context.Context, db *factory.DB) ChartRepository { return (&chart{}).With(ctx, db) @@ -57,16 +57,16 @@ func (r *chart) Create(mod *types.Chart) (*types.Chart, error) { mod.ID = factory.Sonyflake.NextID() mod.CreatedAt = time.Now() - return mod, r.db().Insert("crm_chart", mod) + return mod, r.db().Insert("compose_chart", mod) } func (r *chart) Update(mod *types.Chart) (*types.Chart, error) { now := time.Now() mod.UpdatedAt = &now - return mod, r.db().Replace("crm_chart", mod) + return mod, r.db().Replace("compose_chart", mod) } func (r *chart) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM crm_chart WHERE id = ?", id) + _, err := r.db().Exec("DELETE FROM compose_chart WHERE id = ?", id) return err } diff --git a/crm/internal/repository/flags.go b/compose/internal/repository/flags.go similarity index 100% rename from crm/internal/repository/flags.go rename to compose/internal/repository/flags.go diff --git a/crm/internal/repository/module.go b/compose/internal/repository/module.go similarity index 83% rename from crm/internal/repository/module.go rename to compose/internal/repository/module.go index 007832f09..f7a9299de 100644 --- a/crm/internal/repository/module.go +++ b/compose/internal/repository/module.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ) type ( @@ -33,7 +33,7 @@ const ( created_at, updated_at, deleted_at ` sqlModuleSelect = ` - SELECT ` + sqlModuleColumns + ` FROM crm_module WHERE deleted_at IS NULL + SELECT ` + sqlModuleColumns + ` FROM compose_module WHERE deleted_at IS NULL ` ) @@ -89,7 +89,7 @@ func (r *module) Create(mod *types.Module) (*types.Module, error) { return nil, err } - return mod, r.db().Insert("crm_module", mod) + return mod, r.db().Insert("compose_module", mod) } func (r *module) Update(mod *types.Module) (*types.Module, error) { @@ -100,19 +100,19 @@ func (r *module) Update(mod *types.Module) (*types.Module, error) { return nil, err } - return mod, r.db().Replace("crm_module", mod) + return mod, r.db().Replace("compose_module", mod) } func (r *module) updateFields(moduleID uint64, ff types.ModuleFieldSet) error { // @todo be more selective when deleting - if _, err := r.db().Exec("DELETE FROM crm_module_form WHERE module_id = ?", moduleID); err != nil { + if _, err := r.db().Exec("DELETE FROM compose_module_form WHERE module_id = ?", moduleID); err != nil { return errors.Wrap(err, "Error updating module fields") } for idx, v := range ff { v.ModuleID = moduleID v.Place = idx - if err := r.db().Replace("crm_module_form", v); err != nil { + if err := r.db().Replace("compose_module_form", v); err != nil { return errors.Wrap(err, "Error updating module fields") } } @@ -121,7 +121,7 @@ func (r *module) updateFields(moduleID uint64, ff types.ModuleFieldSet) error { } func (r *module) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM crm_module WHERE id=?", id) + _, err := r.db().Exec("DELETE FROM compose_module WHERE id=?", id) return err } @@ -130,7 +130,7 @@ func (r *module) fields(IDs ...uint64) (ff types.ModuleFieldSet, err error) { return } - if sql, args, err := sqlx.In("SELECT * FROM crm_module_form WHERE module_id IN (?) ORDER BY module_id AND place", IDs); err != nil { + if sql, args, err := sqlx.In("SELECT * FROM compose_module_form WHERE module_id IN (?) ORDER BY module_id AND place", IDs); err != nil { return nil, err } else { return ff, r.db().Select(&ff, sql, args...) diff --git a/crm/internal/repository/page.go b/compose/internal/repository/page.go similarity index 69% rename from crm/internal/repository/page.go rename to compose/internal/repository/page.go index 18881a1a1..bf06d2ae3 100644 --- a/crm/internal/repository/page.go +++ b/compose/internal/repository/page.go @@ -5,7 +5,7 @@ import ( "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ) type ( @@ -42,7 +42,7 @@ func (r *page) With(ctx context.Context, db *factory.DB) PageRepository { func (r *page) FindByID(id uint64) (*types.Page, error) { page := &types.Page{} - if err := r.db().Get(page, "SELECT * FROM crm_page WHERE id=?", id); err != nil { + if err := r.db().Get(page, "SELECT * FROM compose_page WHERE id=?", id); err != nil { return page, err } return page, nil @@ -50,26 +50,26 @@ func (r *page) FindByID(id uint64) (*types.Page, error) { func (r *page) FindByModuleID(id uint64) (*types.Page, error) { page := &types.Page{} - if err := r.db().Get(page, "SELECT * FROM crm_page WHERE module_id=?", id); err != nil { + if err := r.db().Get(page, "SELECT * FROM compose_page WHERE module_id=?", id); err != nil { return nil, err } return page, nil } func (r *page) FindRecordPages() (set types.PageSet, err error) { - return set, r.db().Select(&set, "SELECT * FROM crm_page WHERE module_id > 0") + return set, r.db().Select(&set, "SELECT * FROM compose_page WHERE module_id > 0") } func (r *page) FindBySelfID(selfID uint64) (types.PageSet, error) { pages := types.PageSet{} - if err := r.db().Select(&pages, "SELECT * FROM crm_page WHERE self_id = ? ORDER BY weight ASC", selfID); err != nil { + if err := r.db().Select(&pages, "SELECT * FROM compose_page WHERE self_id = ? ORDER BY weight ASC", selfID); err != nil { return pages, err } return pages, nil } func (r *page) Find() (set types.PageSet, err error) { - return set, r.db().Select(&set, "SELECT * FROM crm_page ORDER BY self_id, weight ASC") + return set, r.db().Select(&set, "SELECT * FROM compose_page ORDER BY self_id, weight ASC") } func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { @@ -87,7 +87,7 @@ func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { for _, pageID := range pageIDs { if pageMap[pageID] { pageMap[pageID] = false - if _, err := db.Exec("UPDATE crm_page set weight=? where id=? and self_id=?", weight, pageID, selfID); err != nil { + if _, err := db.Exec("UPDATE compose_page set weight=? where id=? and self_id=?", weight, pageID, selfID); err != nil { return err } weight++ @@ -95,7 +95,7 @@ func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { } for pageID, update := range pageMap { if update { - if _, err := db.Exec("UPDATE crm_page set weight=? where id=? and self_id=?", weight, pageID, selfID); err != nil { + if _, err := db.Exec("UPDATE compose_page set weight=? where id=? and self_id=?", weight, pageID, selfID); err != nil { return err } weight++ @@ -109,14 +109,14 @@ func (r *page) Create(item *types.Page) (*types.Page, error) { *page = *item page.ID = factory.Sonyflake.NextID() - return page, r.db().Insert("crm_page", page) + return page, r.db().Insert("compose_page", page) } func (r *page) Update(page *types.Page) (*types.Page, error) { - return page, r.db().Replace("crm_page", page) + return page, r.db().Replace("compose_page", page) } func (r *page) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM crm_page WHERE id=?", id) + _, err := r.db().Exec("DELETE FROM compose_page WHERE id=?", id) return err } diff --git a/crm/internal/repository/ql/README.md b/compose/internal/repository/ql/README.md similarity index 100% rename from crm/internal/repository/ql/README.md rename to compose/internal/repository/ql/README.md diff --git a/crm/internal/repository/ql/ast_nodes.go b/compose/internal/repository/ql/ast_nodes.go similarity index 100% rename from crm/internal/repository/ql/ast_nodes.go rename to compose/internal/repository/ql/ast_nodes.go diff --git a/crm/internal/repository/ql/ast_parser.go b/compose/internal/repository/ql/ast_parser.go similarity index 100% rename from crm/internal/repository/ql/ast_parser.go rename to compose/internal/repository/ql/ast_parser.go diff --git a/crm/internal/repository/ql/ast_parser_test.go b/compose/internal/repository/ql/ast_parser_test.go similarity index 100% rename from crm/internal/repository/ql/ast_parser_test.go rename to compose/internal/repository/ql/ast_parser_test.go diff --git a/crm/internal/repository/ql/handlers.go b/compose/internal/repository/ql/handlers.go similarity index 100% rename from crm/internal/repository/ql/handlers.go rename to compose/internal/repository/ql/handlers.go diff --git a/crm/internal/repository/ql/lexer.go b/compose/internal/repository/ql/lexer.go similarity index 100% rename from crm/internal/repository/ql/lexer.go rename to compose/internal/repository/ql/lexer.go diff --git a/crm/internal/repository/ql/lexer_test.go b/compose/internal/repository/ql/lexer_test.go similarity index 100% rename from crm/internal/repository/ql/lexer_test.go rename to compose/internal/repository/ql/lexer_test.go diff --git a/crm/internal/repository/ql/squirrel.go b/compose/internal/repository/ql/squirrel.go similarity index 100% rename from crm/internal/repository/ql/squirrel.go rename to compose/internal/repository/ql/squirrel.go diff --git a/crm/internal/repository/ql/token_codes.go b/compose/internal/repository/ql/token_codes.go similarity index 100% rename from crm/internal/repository/ql/token_codes.go rename to compose/internal/repository/ql/token_codes.go diff --git a/crm/internal/repository/ql/token_consumers.go b/compose/internal/repository/ql/token_consumers.go similarity index 100% rename from crm/internal/repository/ql/token_consumers.go rename to compose/internal/repository/ql/token_consumers.go diff --git a/crm/internal/repository/record.go b/compose/internal/repository/record.go similarity index 83% rename from crm/internal/repository/record.go rename to compose/internal/repository/record.go index 8664b332f..9719259e1 100644 --- a/crm/internal/repository/record.go +++ b/compose/internal/repository/record.go @@ -11,9 +11,9 @@ import ( "github.com/titpetric/factory" sq "gopkg.in/Masterminds/squirrel.v1" - "github.com/crusttech/crust/crm/internal/repository/ql" + "github.com/crusttech/crust/compose/internal/repository/ql" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ) type ( @@ -76,7 +76,7 @@ func (r *record) With(ctx context.Context, db *factory.DB) RecordRepository { func (r *record) FindByID(id uint64) (*types.Record, error) { mod := &types.Record{} - if err := r.db().Get(mod, "SELECT * FROM crm_record WHERE id=? and deleted_at IS NULL", id); err != nil { + if err := r.db().Get(mod, "SELECT * FROM compose_record WHERE id=? and deleted_at IS NULL", id); err != nil { return nil, err } return mod, nil @@ -167,7 +167,7 @@ func (r *record) Find(module *types.Module, filter string, sort string, page int func (r *record) buildQuery(module *types.Module, filter string, sort string) (query sq.SelectBuilder, err error) { // Create query for fetching and counting records. query = sq.Select(). - From("crm_record AS r"). + From("compose_record AS r"). Where(sq.Eq{"r.module_id": module.ID}). Where(sq.Eq{"r.deleted_at": nil}) @@ -206,7 +206,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q if !alreadyJoined(i.Value) { query = query.LeftJoin(fmt.Sprintf( - "crm_record_value AS rv_%s ON (rv_%s.record_id = r.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", + "compose_record_value AS rv_%s ON (rv_%s.record_id = r.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", i.Value, i.Value, i.Value, i.Value, ), i.Value) } @@ -248,7 +248,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q if !alreadyJoined(i.Value) { query = query.LeftJoin(fmt.Sprintf( - "crm_record_value AS rv_%s ON (rv_%s.record_id = r.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", + "compose_record_value AS rv_%s ON (rv_%s.record_id = r.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", i.Value, i.Value, i.Value, i.Value, ), i.Value) } @@ -273,7 +273,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q func (r *record) Create(record *types.Record) (*types.Record, error) { record.ID = factory.Sonyflake.NextID() - if err := r.db().Replace("crm_record", record); err != nil { + if err := r.db().Replace("compose_record", record); err != nil { return nil, errors.Wrap(err, "could not update record") } @@ -281,7 +281,7 @@ func (r *record) Create(record *types.Record) (*types.Record, error) { } func (r *record) Update(record *types.Record) (*types.Record, error) { - if err := r.db().Replace("crm_record", record); err != nil { + if err := r.db().Replace("compose_record", record); err != nil { return nil, errors.Wrap(err, "could not update record") } @@ -290,7 +290,7 @@ func (r *record) Update(record *types.Record) (*types.Record, error) { func (r *record) Delete(record *types.Record) error { _, err := r.db().Exec( - "UPDATE crm_record SET deleted_at = ?, deleted_by = ? WHERE id = ?", + "UPDATE compose_record SET deleted_at = ?, deleted_by = ? WHERE id = ?", record.DeletedAt, record.DeletedBy, record.ID, @@ -301,7 +301,7 @@ func (r *record) Delete(record *types.Record) error { func (r *record) DeleteValues(record *types.Record) error { _, err := r.db().Exec( - "UPDATE crm_record_value SET deleted_at = ? WHERE record_id = ?", + "UPDATE compose_record_value SET deleted_at = ? WHERE record_id = ?", record.DeletedAt, record.ID) @@ -311,13 +311,13 @@ func (r *record) DeleteValues(record *types.Record) error { func (r *record) UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error) { // Remove all records and prepare to be updated // @todo be more selective and delete only removed values - if _, err = r.db().Exec("DELETE FROM crm_record_value WHERE record_id = ?", recordID); err != nil { + if _, err = r.db().Exec("DELETE FROM compose_record_value WHERE record_id = ?", recordID); err != nil { return errors.Wrap(err, "could not remove record values") } err = rvs.Walk(func(value *types.RecordValue) error { value.RecordID = recordID - return r.db().Replace("crm_record_value", value) + return r.db().Replace("compose_record_value", value) }) return errors.Wrap(err, "could not replace record values") @@ -329,7 +329,7 @@ func (r *record) LoadValues(IDs ...uint64) (rvs types.RecordValueSet, err error) return } - var sql = "SELECT * FROM crm_record_value WHERE record_id IN (?) AND deleted_at IS NULL ORDER BY record_id, place" + var sql = "SELECT * FROM compose_record_value WHERE record_id IN (?) AND deleted_at IS NULL ORDER BY record_id, place" if sql, args, err := sqlx.In(sql, IDs); err != nil { return nil, err @@ -370,18 +370,18 @@ func isRealRecordCol(name string) (string, bool) { func (r *record) CountAuthored(userID uint64) (c int, err error) { return c, r.db().Get(&c, - "SELECT COUNT(*) FROM crm_record WHERE created_by = ? OR updated_by = ? OR deleted_by = ?", + "SELECT COUNT(*) FROM compose_record WHERE created_by = ? OR updated_by = ? OR deleted_by = ?", userID, userID, userID) } func (r *record) ChangeAuthor(userID, target uint64) error { - if _, err := r.db().Exec("UPDATE crm_record SET created_by = ? WHERE created_by = ?", target, userID); err != nil { + if _, err := r.db().Exec("UPDATE compose_record SET created_by = ? WHERE created_by = ?", target, userID); err != nil { return err } - if _, err := r.db().Exec("UPDATE crm_record SET updated_by = ? WHERE updated_by = ?", target, userID); err != nil { + if _, err := r.db().Exec("UPDATE compose_record SET updated_by = ? WHERE updated_by = ?", target, userID); err != nil { return err } - if _, err := r.db().Exec("UPDATE crm_record SET deleted_by = ? WHERE deleted_by = ?", target, userID); err != nil { + if _, err := r.db().Exec("UPDATE compose_record SET deleted_by = ? WHERE deleted_by = ?", target, userID); err != nil { return err } @@ -391,11 +391,11 @@ func (r *record) ChangeAuthor(userID, target uint64) error { func (r *record) CountReferenced(userID uint64) (c int, err error) { // @todo add field type (User) check return c, r.db().Get(&c, - "SELECT COUNT(*) FROM crm_record_value WHERE ref = ?", + "SELECT COUNT(*) FROM compose_record_value WHERE ref = ?", userID) } func (r *record) ChangeReferences(userID, target uint64) error { - _, err := r.db().Exec("UPDATE crm_record_value SET ref = ? WHERE ref = ?", target, userID) + _, err := r.db().Exec("UPDATE compose_record_value SET ref = ? WHERE ref = ?", target, userID) return err } diff --git a/crm/internal/repository/record_report_builder.go b/compose/internal/repository/record_report_builder.go similarity index 94% rename from crm/internal/repository/record_report_builder.go rename to compose/internal/repository/record_report_builder.go index 1c31ad9ce..d3b1e036a 100644 --- a/crm/internal/repository/record_report_builder.go +++ b/compose/internal/repository/record_report_builder.go @@ -9,8 +9,8 @@ import ( "github.com/pkg/errors" "gopkg.in/Masterminds/squirrel.v1" - "github.com/crusttech/crust/crm/internal/repository/ql" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/repository/ql" + "github.com/crusttech/crust/compose/types" ) type ( @@ -53,7 +53,7 @@ func NewRecordReportBuilder(module *types.Module) *recordReportBuilder { var report = squirrel. Select(). Column(squirrel.Alias(squirrel.Expr("COUNT(*)"), "count")). - From("crm_record"). + From("compose_record"). Where("module_id = ?", module.ID) return &recordReportBuilder{ @@ -88,7 +88,7 @@ func (b *recordReportBuilder) Build(metrics, dimensions, filters string) (sql st if !alreadyJoined(i.Value) { b.report = b.report.LeftJoin(fmt.Sprintf( - "crm_record_value AS rv_%s ON (rv_%s.record_id = crm_record.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", + "compose_record_value AS rv_%s ON (rv_%s.record_id = compose_record.id AND rv_%s.name = ? AND rv_%s.deleted_at IS NULL)", i.Value, i.Value, i.Value, i.Value, ), i.Value) } diff --git a/crm/internal/repository/record_report_builder_test.go b/compose/internal/repository/record_report_builder_test.go similarity index 71% rename from crm/internal/repository/record_report_builder_test.go rename to compose/internal/repository/record_report_builder_test.go index 58493925b..0c3008aad 100644 --- a/crm/internal/repository/record_report_builder_test.go +++ b/compose/internal/repository/record_report_builder_test.go @@ -5,7 +5,7 @@ package repository import ( "testing" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/test" ) @@ -22,9 +22,9 @@ func TestRecordReportBuilder2(t *testing.T) { expected := "SELECT (COUNT(*)) AS count, (CAST(max(rv_single1.value) AS DECIMAL(14,2))) AS metric_0, " + "(QUARTER(rv_ref1.value)) AS dimension_0 " + - "FROM crm_record " + - "LEFT JOIN crm_record_value AS rv_single1 ON (rv_single1.record_id = crm_record.id AND rv_single1.name = ? AND rv_single1.deleted_at IS NULL) " + - "LEFT JOIN crm_record_value AS rv_ref1 ON (rv_ref1.record_id = crm_record.id AND rv_ref1.name = ? AND rv_ref1.deleted_at IS NULL) " + + "FROM compose_record " + + "LEFT JOIN compose_record_value AS rv_single1 ON (rv_single1.record_id = compose_record.id AND rv_single1.name = ? AND rv_single1.deleted_at IS NULL) " + + "LEFT JOIN compose_record_value AS rv_ref1 ON (rv_ref1.record_id = compose_record.id AND rv_ref1.name = ? AND rv_ref1.deleted_at IS NULL) " + "WHERE module_id = ? AND rv_ref1.value = 2 " + "GROUP BY dimension_0 " + "ORDER BY dimension_0" diff --git a/crm/internal/repository/record_test.go b/compose/internal/repository/record_test.go similarity index 97% rename from crm/internal/repository/record_test.go rename to compose/internal/repository/record_test.go index a60e70eb8..12107aa01 100644 --- a/crm/internal/repository/record_test.go +++ b/compose/internal/repository/record_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/test" ) diff --git a/crm/internal/repository/repository.go b/compose/internal/repository/repository.go similarity index 94% rename from crm/internal/repository/repository.go rename to compose/internal/repository/repository.go index 61eb878b8..fd7418b90 100644 --- a/crm/internal/repository/repository.go +++ b/compose/internal/repository/repository.go @@ -17,7 +17,7 @@ type ( // DB produces a contextual DB handle func DB(ctx context.Context) *factory.DB { - return factory.Database.MustGet("crm").With(ctx) + return factory.Database.MustGet("compose").With(ctx) } // Identity returns the User ID from context diff --git a/crm/internal/repository/repository_test.go b/compose/internal/repository/repository_test.go similarity index 100% rename from crm/internal/repository/repository_test.go rename to compose/internal/repository/repository_test.go diff --git a/crm/internal/repository/sqirrel.go b/compose/internal/repository/sqirrel.go similarity index 100% rename from crm/internal/repository/sqirrel.go rename to compose/internal/repository/sqirrel.go diff --git a/crm/internal/repository/trigger.go b/compose/internal/repository/trigger.go similarity index 88% rename from crm/internal/repository/trigger.go rename to compose/internal/repository/trigger.go index ea38bda57..27a1d4525 100644 --- a/crm/internal/repository/trigger.go +++ b/compose/internal/repository/trigger.go @@ -7,7 +7,7 @@ import ( "github.com/titpetric/factory" "gopkg.in/Masterminds/squirrel.v1" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ) type ( @@ -66,7 +66,7 @@ func (r trigger) query() (query squirrel.SelectBuilder) { Columns( "id", "name", "actions", "enabled", "source", "rel_module", "created_at", "updated_at", "deleted_at"). - From("crm_trigger"). + From("compose_trigger"). OrderBy("id DESC") return @@ -76,16 +76,16 @@ func (r *trigger) Create(mod *types.Trigger) (*types.Trigger, error) { mod.ID = factory.Sonyflake.NextID() mod.CreatedAt = time.Now() - return mod, r.db().Insert("crm_trigger", mod) + return mod, r.db().Insert("compose_trigger", mod) } func (r *trigger) Update(mod *types.Trigger) (*types.Trigger, error) { now := time.Now() mod.UpdatedAt = &now - return mod, r.db().Replace("crm_trigger", mod) + return mod, r.db().Replace("compose_trigger", mod) } func (r *trigger) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM crm_trigger WHERE id = ?", id) + _, err := r.db().Exec("DELETE FROM compose_trigger WHERE id = ?", id) return err } diff --git a/crm/internal/service/attachment.go b/compose/internal/service/attachment.go similarity index 98% rename from crm/internal/service/attachment.go rename to compose/internal/service/attachment.go index 482b61732..7b7af28a5 100644 --- a/crm/internal/service/attachment.go +++ b/compose/internal/service/attachment.go @@ -16,8 +16,8 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/internal/repository" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/store" diff --git a/crm/internal/service/chart.go b/compose/internal/service/chart.go similarity index 96% rename from crm/internal/service/chart.go rename to compose/internal/service/chart.go index 2335a9bd0..c1684c0f3 100644 --- a/crm/internal/service/chart.go +++ b/compose/internal/service/chart.go @@ -6,8 +6,8 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/internal/repository" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" ) type ( diff --git a/crm/internal/service/chart_test.go b/compose/internal/service/chart_test.go similarity index 100% rename from crm/internal/service/chart_test.go rename to compose/internal/service/chart_test.go diff --git a/crm/internal/service/main_test.go b/compose/internal/service/main_test.go similarity index 77% rename from crm/internal/service/main_test.go rename to compose/internal/service/main_test.go index d6e964b23..d71f79a2f 100644 --- a/crm/internal/service/main_test.go +++ b/compose/internal/service/main_test.go @@ -10,7 +10,7 @@ import ( "github.com/namsral/flag" "github.com/titpetric/factory" - crmMigrate "github.com/crusttech/crust/crm/db" + composeMigrate "github.com/crusttech/crust/compose/db" systemMigrate "github.com/crusttech/crust/system/db" systemService "github.com/crusttech/crust/system/service" ) @@ -25,7 +25,7 @@ func TestMain(m *testing.M) { flag.Parse() factory.Database.Add("default", dsn) - factory.Database.Add("crm", dsn) + factory.Database.Add("compose", dsn) factory.Database.Add("system", dsn) db := factory.Database.MustGet() @@ -36,14 +36,14 @@ func TestMain(m *testing.M) { log.Printf("Error running migrations: %+v\n", err) return } - if err := crmMigrate.Migrate(db); err != nil { + if err := composeMigrate.Migrate(db); err != nil { log.Printf("Error running migrations: %+v\n", err) return } // clean up tables { - for _, name := range []string{"crm_chart", "crm_trigger", "crm_module", "crm_module_form", "crm_record", "crm_record_value", "crm_page", "sys_user"} { + for _, name := range []string{"compose_chart", "compose_trigger", "compose_module", "compose_module_form", "compose_record", "compose_record_value", "compose_page", "sys_user"} { _, err := db.Exec("truncate " + name) if err != nil { panic("Error when clearing " + name + ": " + err.Error()) diff --git a/crm/internal/service/module.go b/compose/internal/service/module.go similarity index 96% rename from crm/internal/service/module.go rename to compose/internal/service/module.go index 791556b03..f687335fd 100644 --- a/crm/internal/service/module.go +++ b/compose/internal/service/module.go @@ -6,8 +6,8 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/internal/repository" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" ) type ( diff --git a/crm/internal/service/module_test.go b/compose/internal/service/module_test.go similarity index 98% rename from crm/internal/service/module_test.go rename to compose/internal/service/module_test.go index 678bf8698..55d8ab445 100644 --- a/crm/internal/service/module_test.go +++ b/compose/internal/service/module_test.go @@ -6,7 +6,7 @@ import ( "context" "testing" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/test" systemTypes "github.com/crusttech/crust/system/types" diff --git a/crm/internal/service/notification.go b/compose/internal/service/notification.go similarity index 100% rename from crm/internal/service/notification.go rename to compose/internal/service/notification.go diff --git a/crm/internal/service/page.go b/compose/internal/service/page.go similarity index 97% rename from crm/internal/service/page.go rename to compose/internal/service/page.go index ca5ac998d..9e6869218 100644 --- a/crm/internal/service/page.go +++ b/compose/internal/service/page.go @@ -6,8 +6,8 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/internal/repository" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" ) type ( diff --git a/crm/internal/service/page_test.go b/compose/internal/service/page_test.go similarity index 99% rename from crm/internal/service/page_test.go rename to compose/internal/service/page_test.go index d0a221447..6e4acddcf 100644 --- a/crm/internal/service/page_test.go +++ b/compose/internal/service/page_test.go @@ -9,7 +9,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/test" systemTypes "github.com/crusttech/crust/system/types" diff --git a/crm/internal/service/permissions.go b/compose/internal/service/permissions.go similarity index 98% rename from crm/internal/service/permissions.go rename to compose/internal/service/permissions.go index e4bf725a9..dcec64e95 100644 --- a/crm/internal/service/permissions.go +++ b/compose/internal/service/permissions.go @@ -3,8 +3,8 @@ package service import ( "context" - "github.com/crusttech/crust/crm/internal/repository" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" internalRules "github.com/crusttech/crust/internal/rules" systemService "github.com/crusttech/crust/system/service" ) diff --git a/crm/internal/service/permissions_test.go b/compose/internal/service/permissions_test.go similarity index 95% rename from crm/internal/service/permissions_test.go rename to compose/internal/service/permissions_test.go index add7de9f0..05b1c5150 100644 --- a/crm/internal/service/permissions_test.go +++ b/compose/internal/service/permissions_test.go @@ -6,8 +6,8 @@ import ( "context" "testing" - "github.com/crusttech/crust/crm/internal/repository" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/rules" . "github.com/crusttech/crust/internal/test" diff --git a/crm/internal/service/record.go b/compose/internal/service/record.go similarity index 98% rename from crm/internal/service/record.go rename to compose/internal/service/record.go index 249d5e6a1..1b9a44445 100644 --- a/crm/internal/service/record.go +++ b/compose/internal/service/record.go @@ -8,8 +8,8 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/internal/repository" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" systemService "github.com/crusttech/crust/system/service" diff --git a/crm/internal/service/record_test.go b/compose/internal/service/record_test.go similarity index 99% rename from crm/internal/service/record_test.go rename to compose/internal/service/record_test.go index f393ac293..a0c8ee26e 100644 --- a/crm/internal/service/record_test.go +++ b/compose/internal/service/record_test.go @@ -6,7 +6,7 @@ import ( "context" "testing" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/test" systemService "github.com/crusttech/crust/system/service" diff --git a/crm/internal/service/service.go b/compose/internal/service/service.go similarity index 100% rename from crm/internal/service/service.go rename to compose/internal/service/service.go diff --git a/crm/internal/service/trigger.go b/compose/internal/service/trigger.go similarity index 96% rename from crm/internal/service/trigger.go rename to compose/internal/service/trigger.go index ecd2cc54d..ac5e51a8a 100644 --- a/crm/internal/service/trigger.go +++ b/compose/internal/service/trigger.go @@ -6,8 +6,8 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" - "github.com/crusttech/crust/crm/internal/repository" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" ) type ( diff --git a/crm/internal/service/trigger_test.go b/compose/internal/service/trigger_test.go similarity index 98% rename from crm/internal/service/trigger_test.go rename to compose/internal/service/trigger_test.go index d9b9c51aa..917113c1e 100644 --- a/crm/internal/service/trigger_test.go +++ b/compose/internal/service/trigger_test.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/test" systemTypes "github.com/crusttech/crust/system/types" diff --git a/crm/rest/attachment.go b/compose/rest/attachment.go similarity index 96% rename from crm/rest/attachment.go rename to compose/rest/attachment.go index 5b4bb1b37..7f9babf0a 100644 --- a/crm/rest/attachment.go +++ b/compose/rest/attachment.go @@ -8,9 +8,9 @@ import ( "net/url" "time" - "github.com/crusttech/crust/crm/internal/service" - "github.com/crusttech/crust/crm/rest/request" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" "github.com/pkg/errors" diff --git a/crm/rest/chart.go b/compose/rest/chart.go similarity index 88% rename from crm/rest/chart.go rename to compose/rest/chart.go index 4dc54af80..e38f84016 100644 --- a/crm/rest/chart.go +++ b/compose/rest/chart.go @@ -5,9 +5,9 @@ import ( "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/internal/service" - "github.com/crusttech/crust/crm/rest/request" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" + "github.com/crusttech/crust/compose/types" "github.com/pkg/errors" ) diff --git a/crm/rest/handlers/attachment.go b/compose/rest/handlers/attachment.go similarity index 98% rename from crm/rest/handlers/attachment.go rename to compose/rest/handlers/attachment.go index 9a1225521..502825af7 100644 --- a/crm/rest/handlers/attachment.go +++ b/compose/rest/handlers/attachment.go @@ -23,7 +23,7 @@ import ( "github.com/go-chi/chi" "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/rest/request" ) // Internal API interface diff --git a/crm/rest/handlers/chart.go b/compose/rest/handlers/chart.go similarity index 98% rename from crm/rest/handlers/chart.go rename to compose/rest/handlers/chart.go index 7dc5880e8..b758d2f19 100644 --- a/crm/rest/handlers/chart.go +++ b/compose/rest/handlers/chart.go @@ -23,7 +23,7 @@ import ( "github.com/go-chi/chi" "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/rest/request" ) // Internal API interface diff --git a/crm/rest/handlers/module.go b/compose/rest/handlers/module.go similarity index 98% rename from crm/rest/handlers/module.go rename to compose/rest/handlers/module.go index ad8f4bad8..7006744b9 100644 --- a/crm/rest/handlers/module.go +++ b/compose/rest/handlers/module.go @@ -23,7 +23,7 @@ import ( "github.com/go-chi/chi" "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/rest/request" ) // Internal API interface diff --git a/crm/rest/handlers/notification.go b/compose/rest/handlers/notification.go similarity index 97% rename from crm/rest/handlers/notification.go rename to compose/rest/handlers/notification.go index 802382de4..ce176b119 100644 --- a/crm/rest/handlers/notification.go +++ b/compose/rest/handlers/notification.go @@ -23,7 +23,7 @@ import ( "github.com/go-chi/chi" "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/rest/request" ) // Internal API interface diff --git a/crm/rest/handlers/page.go b/compose/rest/handlers/page.go similarity index 99% rename from crm/rest/handlers/page.go rename to compose/rest/handlers/page.go index 2e5b2b3ef..a4b19af44 100644 --- a/crm/rest/handlers/page.go +++ b/compose/rest/handlers/page.go @@ -23,7 +23,7 @@ import ( "github.com/go-chi/chi" "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/rest/request" ) // Internal API interface diff --git a/crm/rest/handlers/permissions.go b/compose/rest/handlers/permissions.go similarity index 97% rename from crm/rest/handlers/permissions.go rename to compose/rest/handlers/permissions.go index 09fa705f4..b6fd05a53 100644 --- a/crm/rest/handlers/permissions.go +++ b/compose/rest/handlers/permissions.go @@ -23,7 +23,7 @@ import ( "github.com/go-chi/chi" "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/rest/request" ) // Internal API interface diff --git a/crm/rest/handlers/record.go b/compose/rest/handlers/record.go similarity index 99% rename from crm/rest/handlers/record.go rename to compose/rest/handlers/record.go index e80b7b067..f2c652cb0 100644 --- a/crm/rest/handlers/record.go +++ b/compose/rest/handlers/record.go @@ -23,7 +23,7 @@ import ( "github.com/go-chi/chi" "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/rest/request" ) // Internal API interface diff --git a/crm/rest/handlers/trigger.go b/compose/rest/handlers/trigger.go similarity index 98% rename from crm/rest/handlers/trigger.go rename to compose/rest/handlers/trigger.go index d5e6108ae..6aaea99fa 100644 --- a/crm/rest/handlers/trigger.go +++ b/compose/rest/handlers/trigger.go @@ -23,7 +23,7 @@ import ( "github.com/go-chi/chi" "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/rest/request" ) // Internal API interface diff --git a/crm/rest/middleware.go b/compose/rest/middleware.go similarity index 86% rename from crm/rest/middleware.go rename to compose/rest/middleware.go index 0277d619e..4ed310faa 100644 --- a/crm/rest/middleware.go +++ b/compose/rest/middleware.go @@ -3,7 +3,7 @@ package rest import ( "net/http" - "github.com/crusttech/crust/crm/internal/service" + "github.com/crusttech/crust/compose/internal/service" ) func middlewareAllowedAccess(next http.Handler) http.Handler { diff --git a/crm/rest/module.go b/compose/rest/module.go similarity index 88% rename from crm/rest/module.go rename to compose/rest/module.go index 591b15f3a..63d966058 100644 --- a/crm/rest/module.go +++ b/compose/rest/module.go @@ -5,9 +5,9 @@ import ( "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/internal/service" - "github.com/crusttech/crust/crm/rest/request" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" + "github.com/crusttech/crust/compose/types" ) type ( diff --git a/crm/rest/notification.go b/compose/rest/notification.go similarity index 92% rename from crm/rest/notification.go rename to compose/rest/notification.go index 7d316e5e5..52a9f706c 100644 --- a/crm/rest/notification.go +++ b/compose/rest/notification.go @@ -3,8 +3,8 @@ package rest import ( "context" - "github.com/crusttech/crust/crm/internal/service" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" "github.com/crusttech/crust/internal/mail" "github.com/pkg/errors" diff --git a/crm/rest/page.go b/compose/rest/page.go similarity index 94% rename from crm/rest/page.go rename to compose/rest/page.go index b0b83ba32..4d42aed83 100644 --- a/crm/rest/page.go +++ b/compose/rest/page.go @@ -5,9 +5,9 @@ import ( "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/internal/service" - "github.com/crusttech/crust/crm/rest/request" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/payload" ) diff --git a/crm/rest/permissions.go b/compose/rest/permissions.go similarity index 80% rename from crm/rest/permissions.go rename to compose/rest/permissions.go index a7f8680b4..7d5b3556d 100644 --- a/crm/rest/permissions.go +++ b/compose/rest/permissions.go @@ -3,8 +3,8 @@ package rest import ( "context" - "github.com/crusttech/crust/crm/internal/service" - "github.com/crusttech/crust/crm/rest/request" + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" "github.com/pkg/errors" ) diff --git a/crm/rest/record.go b/compose/rest/record.go similarity index 93% rename from crm/rest/record.go rename to compose/rest/record.go index f8c80b9ad..c565b22be 100644 --- a/crm/rest/record.go +++ b/compose/rest/record.go @@ -5,9 +5,9 @@ import ( "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/internal/service" - "github.com/crusttech/crust/crm/rest/request" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" + "github.com/crusttech/crust/compose/types" "github.com/crusttech/crust/internal/auth" "github.com/pkg/errors" diff --git a/crm/rest/request/attachment.go b/compose/rest/request/attachment.go similarity index 100% rename from crm/rest/request/attachment.go rename to compose/rest/request/attachment.go diff --git a/crm/rest/request/chart.go b/compose/rest/request/chart.go similarity index 100% rename from crm/rest/request/chart.go rename to compose/rest/request/chart.go diff --git a/crm/rest/request/misc.go b/compose/rest/request/misc.go similarity index 100% rename from crm/rest/request/misc.go rename to compose/rest/request/misc.go diff --git a/crm/rest/request/module.go b/compose/rest/request/module.go similarity index 99% rename from crm/rest/request/module.go rename to compose/rest/request/module.go index 28cdd1e3c..e6cc06033 100644 --- a/crm/rest/request/module.go +++ b/compose/rest/request/module.go @@ -26,7 +26,7 @@ import ( "github.com/go-chi/chi" "github.com/pkg/errors" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" sqlxTypes "github.com/jmoiron/sqlx/types" ) diff --git a/crm/rest/request/notification.go b/compose/rest/request/notification.go similarity index 100% rename from crm/rest/request/notification.go rename to compose/rest/request/notification.go diff --git a/crm/rest/request/page.go b/compose/rest/request/page.go similarity index 100% rename from crm/rest/request/page.go rename to compose/rest/request/page.go diff --git a/crm/rest/request/permissions.go b/compose/rest/request/permissions.go similarity index 100% rename from crm/rest/request/permissions.go rename to compose/rest/request/permissions.go diff --git a/crm/rest/request/record.go b/compose/rest/request/record.go similarity index 99% rename from crm/rest/request/record.go rename to compose/rest/request/record.go index 1cf3164db..b15d50fe3 100644 --- a/crm/rest/request/record.go +++ b/compose/rest/request/record.go @@ -26,7 +26,7 @@ import ( "github.com/go-chi/chi" "github.com/pkg/errors" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/types" ) var _ = chi.URLParam diff --git a/crm/rest/request/trigger.go b/compose/rest/request/trigger.go similarity index 100% rename from crm/rest/request/trigger.go rename to compose/rest/request/trigger.go diff --git a/crm/rest/request/util.go b/compose/rest/request/util.go similarity index 100% rename from crm/rest/request/util.go rename to compose/rest/request/util.go diff --git a/crm/rest/request/util_test.go b/compose/rest/request/util_test.go similarity index 100% rename from crm/rest/request/util_test.go rename to compose/rest/request/util_test.go diff --git a/crm/rest/router.go b/compose/rest/router.go similarity index 95% rename from crm/rest/router.go rename to compose/rest/router.go index b8dde6b61..a0918fe54 100644 --- a/crm/rest/router.go +++ b/compose/rest/router.go @@ -3,7 +3,7 @@ package rest import ( "github.com/go-chi/chi" - "github.com/crusttech/crust/crm/rest/handlers" + "github.com/crusttech/crust/compose/rest/handlers" "github.com/crusttech/crust/internal/auth" ) diff --git a/crm/rest/trigger.go b/compose/rest/trigger.go similarity index 91% rename from crm/rest/trigger.go rename to compose/rest/trigger.go index d91a55833..cee170569 100644 --- a/crm/rest/trigger.go +++ b/compose/rest/trigger.go @@ -5,9 +5,9 @@ import ( "github.com/titpetric/factory/resputil" - "github.com/crusttech/crust/crm/internal/service" - "github.com/crusttech/crust/crm/rest/request" - "github.com/crusttech/crust/crm/types" + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" + "github.com/crusttech/crust/compose/types" "github.com/pkg/errors" ) diff --git a/crm/routes.go b/compose/routes.go similarity index 95% rename from crm/routes.go rename to compose/routes.go index 4d5e30f6d..f71f5ce21 100644 --- a/crm/routes.go +++ b/compose/routes.go @@ -5,7 +5,7 @@ import ( "github.com/go-chi/chi" - "github.com/crusttech/crust/crm/rest" + "github.com/crusttech/crust/compose/rest" "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/config" "github.com/crusttech/crust/internal/middleware" diff --git a/crm/start.go b/compose/start.go similarity index 90% rename from crm/start.go rename to compose/start.go index decbce70f..073d59dd3 100644 --- a/crm/start.go +++ b/compose/start.go @@ -10,8 +10,8 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory/resputil" - migrate "github.com/crusttech/crust/crm/db" - "github.com/crusttech/crust/crm/internal/service" + migrate "github.com/crusttech/crust/compose/db" + "github.com/crusttech/crust/compose/internal/service" "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/db" "github.com/crusttech/crust/internal/mail" @@ -52,7 +52,7 @@ func Init(ctx context.Context) (err error) { func InitDatabase(ctx context.Context) error { // start/configure database connection - db, err := db.TryToConnect(ctx, "crm", flags.db.DSN, flags.db.Profiler) + db, err := db.TryToConnect(ctx, "compose", flags.db.DSN, flags.db.Profiler) if err != nil { return errors.Wrap(err, "could not connect to database") } diff --git a/crm/types/attachment.gen.go b/compose/types/attachment.gen.go similarity index 100% rename from crm/types/attachment.gen.go rename to compose/types/attachment.gen.go diff --git a/crm/types/attachment.go b/compose/types/attachment.go similarity index 100% rename from crm/types/attachment.go rename to compose/types/attachment.go diff --git a/crm/types/chart.gen.go b/compose/types/chart.gen.go similarity index 100% rename from crm/types/chart.gen.go rename to compose/types/chart.gen.go diff --git a/crm/types/chart.go b/compose/types/chart.go similarity index 94% rename from crm/types/chart.go rename to compose/types/chart.go index d4bdcd3f3..03a44486f 100644 --- a/crm/types/chart.go +++ b/compose/types/chart.go @@ -9,7 +9,6 @@ import ( ) type ( - // Modules - CRM module definitions Chart struct { ID uint64 `json:"chartID,string" db:"id"` Name string `json:"name" db:"name"` diff --git a/crm/types/module.gen.go b/compose/types/module.gen.go similarity index 100% rename from crm/types/module.gen.go rename to compose/types/module.gen.go diff --git a/crm/types/module.go b/compose/types/module.go similarity index 95% rename from crm/types/module.go rename to compose/types/module.go index 638be5d49..1a73f0d94 100644 --- a/crm/types/module.go +++ b/compose/types/module.go @@ -9,7 +9,6 @@ import ( ) type ( - // Modules - CRM module definitions Module struct { ID uint64 `json:"moduleID,string" db:"id"` Name string `json:"name" db:"name"` diff --git a/crm/types/module_field.gen.go b/compose/types/module_field.gen.go similarity index 100% rename from crm/types/module_field.gen.go rename to compose/types/module_field.gen.go diff --git a/crm/types/module_field.go b/compose/types/module_field.go similarity index 100% rename from crm/types/module_field.go rename to compose/types/module_field.go diff --git a/crm/types/namespace.go b/compose/types/namespace.go similarity index 100% rename from crm/types/namespace.go rename to compose/types/namespace.go diff --git a/crm/types/page.gen.go b/compose/types/page.gen.go similarity index 100% rename from crm/types/page.gen.go rename to compose/types/page.gen.go diff --git a/crm/types/page.go b/compose/types/page.go similarity index 100% rename from crm/types/page.go rename to compose/types/page.go diff --git a/crm/types/permission_resources.go b/compose/types/permission_resources.go similarity index 100% rename from crm/types/permission_resources.go rename to compose/types/permission_resources.go diff --git a/crm/types/record.gen.go b/compose/types/record.gen.go similarity index 100% rename from crm/types/record.gen.go rename to compose/types/record.gen.go diff --git a/crm/types/record.go b/compose/types/record.go similarity index 100% rename from crm/types/record.go rename to compose/types/record.go diff --git a/crm/types/record_value.gen.go b/compose/types/record_value.gen.go similarity index 100% rename from crm/types/record_value.gen.go rename to compose/types/record_value.gen.go diff --git a/crm/types/record_value.go b/compose/types/record_value.go similarity index 100% rename from crm/types/record_value.go rename to compose/types/record_value.go diff --git a/crm/types/trigger.gen.go b/compose/types/trigger.gen.go similarity index 100% rename from crm/types/trigger.gen.go rename to compose/types/trigger.gen.go diff --git a/crm/types/trigger.go b/compose/types/trigger.go similarity index 100% rename from crm/types/trigger.go rename to compose/types/trigger.go diff --git a/docs/crm/README.md b/docs/crm/README.md deleted file mode 100644 index 7d2224c2f..000000000 --- a/docs/crm/README.md +++ /dev/null @@ -1,693 +0,0 @@ -# Attachments - -| Method | Endpoint | Purpose | -| ------ | -------- | ------- | -| `GET` | `/attachment/{kind}/` | List, filter all page attachments | -| `GET` | `/attachment/{kind}/{attachmentID}` | Attachment details | -| `GET` | `/attachment/{kind}/{attachmentID}/original/{name}` | Serves attached file | -| `GET` | `/attachment/{kind}/{attachmentID}/preview.{ext}` | Serves preview of an attached file | - -## List, filter all page attachments - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/attachment/{kind}/` | HTTP/S | GET | Client ID, Session ID | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| pageID | uint64 | GET | Filter attachments by page ID | N/A | NO | -| moduleID | uint64 | GET | Filter attachments by mnodule ID | N/A | NO | -| recordID | uint64 | GET | Filter attachments by record ID | N/A | NO | -| fieldName | string | GET | Filter attachments by field name | N/A | NO | -| page | uint | GET | Page number (0 based) | N/A | NO | -| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | -| sign | string | GET | Signature | N/A | YES | -| userID | uint64 | GET | User ID | N/A | YES | -| kind | string | PATH | Attachment kind | N/A | YES | - -## Attachment details - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/attachment/{kind}/{attachmentID}` | HTTP/S | GET | Client ID, Session ID | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | -| kind | string | PATH | Attachment kind | N/A | YES | -| sign | string | GET | Signature | N/A | YES | -| userID | uint64 | GET | User ID | N/A | YES | - -## Serves attached file - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/attachment/{kind}/{attachmentID}/original/{name}` | HTTP/S | GET | Client ID, Session ID | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| download | bool | GET | Force file download | N/A | NO | -| sign | string | GET | Signature | N/A | YES | -| userID | uint64 | GET | User ID | N/A | YES | -| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | -| name | string | PATH | File name | N/A | YES | -| kind | string | PATH | Attachment kind | N/A | YES | - -## Serves preview of an attached file - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/attachment/{kind}/{attachmentID}/preview.{ext}` | HTTP/S | GET | Client ID, Session ID | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | -| ext | string | PATH | Preview extension/format | N/A | YES | -| kind | string | PATH | Attachment kind | N/A | YES | -| sign | string | GET | Signature | N/A | YES | -| userID | uint64 | GET | User ID | N/A | YES | - ---- - - - - -# Charts - -| Method | Endpoint | Purpose | -| ------ | -------- | ------- | -| `GET` | `/chart/` | List/read charts from module section | -| `POST` | `/chart/` | List/read charts from module section | -| `GET` | `/chart/{chartID}` | Read charts by ID from module section | -| `POST` | `/chart/{chartID}` | Add/update charts in module section | -| `DELETE` | `/chart/{chartID}` | Delete chart | - -## List/read charts from module section - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/chart/` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | - -## List/read charts from module section - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/chart/` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| config | sqlxTypes.JSONText | POST | Chart JSON | N/A | YES | -| name | string | POST | Chart name | N/A | YES | - -## Read charts by ID from module section - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/chart/{chartID}` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| chartID | uint64 | PATH | Chart ID | N/A | YES | - -## Add/update charts in module section - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/chart/{chartID}` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| chartID | uint64 | PATH | Chart ID | N/A | YES | -| config | sqlxTypes.JSONText | POST | Chart JSON | N/A | YES | -| name | string | POST | Chart name | N/A | YES | - -## Delete chart - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/chart/{chartID}` | HTTP/S | DELETE | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| chartID | uint64 | PATH | Chart ID | N/A | YES | - ---- - - - - -# Modules - -CRM module definitions - -| Method | Endpoint | Purpose | -| ------ | -------- | ------- | -| `GET` | `/module/` | List modules | -| `POST` | `/module/` | Create module | -| `GET` | `/module/{moduleID}` | Read module | -| `POST` | `/module/{moduleID}` | Update module | -| `DELETE` | `/module/{moduleID}` | Delete module | - -## List modules - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| query | string | GET | Search query | N/A | NO | - -## Create module - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| name | string | POST | Module Name | N/A | YES | -| fields | types.ModuleFieldSet | POST | Fields JSON | N/A | YES | -| meta | sqlxTypes.JSONText | POST | Module meta data | N/A | YES | - -## Read module - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| moduleID | uint64 | PATH | Module ID | N/A | YES | - -## Update module - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| moduleID | uint64 | PATH | Module ID | N/A | YES | -| name | string | POST | Module Name | N/A | YES | -| fields | types.ModuleFieldSet | POST | Fields JSON | N/A | YES | -| meta | sqlxTypes.JSONText | POST | Module meta data | N/A | YES | - -## Delete module - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}` | HTTP/S | DELETE | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| moduleID | uint64 | PATH | Module ID | N/A | YES | - ---- - - - - -# Notifications - -CRM Notifications - -| Method | Endpoint | Purpose | -| ------ | -------- | ------- | -| `POST` | `/notification/email` | Send email from the CRM | - -## Send email from the CRM - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/notification/email` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| to | []string | POST | Email addresses or Crust user IDs | N/A | YES | -| cc | []string | POST | Email addresses or Crust user IDs | N/A | NO | -| replyTo | string | POST | Crust user ID or email address in reply-to field | N/A | NO | -| subject | string | POST | Email subject | N/A | NO | -| content | sqlxTypes.JSONText | POST | Message content | N/A | YES | - ---- - - - - -# Pages - -CRM module pages - -| Method | Endpoint | Purpose | -| ------ | -------- | ------- | -| `GET` | `/page/` | List available pages | -| `POST` | `/page/` | Create page | -| `GET` | `/page/{pageID}` | Get page details | -| `GET` | `/page/tree` | Get page all (non-record) pages, hierarchically | -| `POST` | `/page/{pageID}` | Update page | -| `POST` | `/page/{selfID}/reorder` | Reorder pages | -| `Delete` | `/page/{pageID}` | Delete page | -| `POST` | `/page/{pageID}/attachment` | Uploads attachment to page | - -## List available pages - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/page/` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| selfID | uint64 | GET | Parent page ID | N/A | NO | - -## Create page - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/page/` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| selfID | uint64 | POST | Parent Page ID | N/A | NO | -| moduleID | uint64 | POST | Module ID | N/A | NO | -| title | string | POST | Title | N/A | YES | -| description | string | POST | Description | N/A | NO | -| visible | bool | POST | Visible in navigation | N/A | NO | -| blocks | sqlxTypes.JSONText | POST | Blocks JSON | N/A | YES | - -## Get page details - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/page/{pageID}` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| pageID | uint64 | PATH | Page ID | N/A | YES | - -## Get page all (non-record) pages, hierarchically - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/page/tree` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | - -## Update page - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/page/{pageID}` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| pageID | uint64 | PATH | Page ID | N/A | YES | -| selfID | uint64 | POST | Parent Page ID | N/A | NO | -| moduleID | uint64 | POST | Module ID (optional) | N/A | NO | -| title | string | POST | Title | N/A | YES | -| description | string | POST | Description | N/A | NO | -| visible | bool | POST | Visible in navigation | N/A | NO | -| blocks | sqlxTypes.JSONText | POST | Blocks JSON | N/A | YES | - -## Reorder pages - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/page/{selfID}/reorder` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| selfID | uint64 | PATH | Parent page ID | N/A | YES | -| pageIDs | []string | POST | Page ID order | N/A | YES | - -## Delete page - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/page/{pageID}` | HTTP/S | Delete | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| pageID | uint64 | PATH | Page ID | N/A | YES | - -## Uploads attachment to page - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/page/{pageID}/attachment` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| pageID | uint64 | PATH | Page ID | N/A | YES | -| upload | *multipart.FileHeader | POST | File to upload | N/A | YES | - ---- - - - - -# Permissions - -| Method | Endpoint | Purpose | -| ------ | -------- | ------- | -| `GET` | `/permissions/effective` | Effective rules for current user | - -## Effective rules for current user - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/permissions/effective` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| resource | string | GET | Show only rules for a specific resource | N/A | NO | - ---- - - - - -# Records - -CRM records - -| Method | Endpoint | Purpose | -| ------ | -------- | ------- | -| `GET` | `/module/{moduleID}/record/report` | Generates report from module records | -| `GET` | `/module/{moduleID}/record/` | List/read records from module section | -| `POST` | `/module/{moduleID}/record/` | Create record in module section | -| `GET` | `/module/{moduleID}/record/{recordID}` | Read records by ID from module section | -| `POST` | `/module/{moduleID}/record/{recordID}` | Update records in module section | -| `DELETE` | `/module/{moduleID}/record/{recordID}` | Delete record row from module section | -| `POST` | `/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | Uploads attachment and validates it against record field requirements | - -## Generates report from module records - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/report` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| metrics | string | GET | Metrics (eg: 'SUM(money), MAX(calls)') | N/A | NO | -| dimensions | string | GET | Dimensions (eg: 'DATE(foo), status') | N/A | YES | -| filter | string | GET | Filter (eg: 'DATE(foo) > 2010') | N/A | NO | -| moduleID | uint64 | PATH | Module ID | N/A | YES | - -## List/read records from module section - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| filter | string | GET | Filtering condition | N/A | NO | -| page | int | GET | Page number (0 based) | N/A | NO | -| perPage | int | GET | Returned items per page (default 50) | N/A | NO | -| sort | string | GET | Sort field (default id desc) | N/A | NO | -| moduleID | uint64 | PATH | Module ID | N/A | YES | - -## Create record in module section - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| values | types.RecordValueSet | POST | Record values | N/A | YES | -| moduleID | uint64 | PATH | Module ID | N/A | YES | - -## Read records by ID from module section - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/{recordID}` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| recordID | uint64 | PATH | Record ID | N/A | YES | -| moduleID | uint64 | PATH | Module ID | N/A | YES | - -## Update records in module section - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/{recordID}` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| recordID | uint64 | PATH | Record ID | N/A | YES | -| moduleID | uint64 | PATH | Module ID | N/A | YES | -| values | types.RecordValueSet | POST | Record values | N/A | YES | - -## Delete record row from module section - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/{recordID}` | HTTP/S | DELETE | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| recordID | uint64 | PATH | Record ID | N/A | YES | -| moduleID | uint64 | PATH | Module ID | N/A | YES | - -## Uploads attachment and validates it against record field requirements - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| recordID | uint64 | PATH | Record ID | N/A | YES | -| fieldName | string | PATH | Field name | N/A | YES | -| moduleID | uint64 | PATH | Module ID | N/A | YES | -| upload | *multipart.FileHeader | POST | File to upload | N/A | YES | - ---- - - - - -# Triggers - -CRM Triggers - -| Method | Endpoint | Purpose | -| ------ | -------- | ------- | -| `GET` | `/trigger/` | List available triggers | -| `POST` | `/trigger/` | Create trigger | -| `GET` | `/trigger/{triggerID}` | Get trigger details | -| `POST` | `/trigger/{triggerID}` | Update trigger | -| `Delete` | `/trigger/{triggerID}` | Delete trigger | - -## List available triggers - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/trigger/` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| moduleID | uint64 | GET | Filter triggers by module | N/A | NO | - -## Create trigger - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/trigger/` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| moduleID | uint64 | POST | Module ID | N/A | NO | -| name | string | POST | Name | N/A | YES | -| actions | []string | POST | Actions that trigger this trigger | N/A | NO | -| enabled | bool | POST | Enabled | N/A | NO | -| source | string | POST | Trigger source code | N/A | NO | - -## Get trigger details - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/trigger/{triggerID}` | HTTP/S | GET | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| triggerID | uint64 | PATH | Trigger ID | N/A | YES | - -## Update trigger - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/trigger/{triggerID}` | HTTP/S | POST | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| triggerID | uint64 | PATH | Trigger ID | N/A | YES | -| moduleID | uint64 | POST | Module ID | N/A | NO | -| name | string | POST | Name | N/A | YES | -| actions | []string | POST | Actions that trigger this trigger | N/A | NO | -| enabled | bool | POST | Enabled | N/A | NO | -| source | string | POST | Trigger source code | N/A | NO | - -## Delete trigger - -#### Method - -| URI | Protocol | Method | Authentication | -| --- | -------- | ------ | -------------- | -| `/trigger/{triggerID}` | HTTP/S | Delete | | - -#### Request parameters - -| Parameter | Type | Method | Description | Default | Required? | -| --------- | ---- | ------ | ----------- | ------- | --------- | -| triggerID | uint64 | PATH | Trigger ID | N/A | YES | - ---- \ No newline at end of file From 5bfbab6a4ef62487ffc598d30050ecb109f07302 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sun, 28 Apr 2019 08:56:44 +0200 Subject: [PATCH 02/14] Implement namespace CRUD + various small fixes Other fixes and improvements: - add parseISODateWithErr and parseISODatePtrWithErr handlers for incoming data - add service & repository errors - cleanup old (unbound) attachment controllers from router - fix system repository error prefix (auth => system) --- api/compose/spec.json | 161 +++- api/compose/spec/namespace.json | 160 ++++ codegen.sh | 1 + codegen/codegen.php | 2 + compose/db/mysql/static.go | 2 +- .../mysql/20190427210922.namespace-tbl.up.sql | 14 + compose/internal/repository/error.go | 25 + compose/internal/repository/namespace.go | 168 ++++ compose/internal/service/error.go | 31 + compose/internal/service/namespace.go | 132 +++ compose/internal/service/permissions.go | 32 +- compose/internal/service/service.go | 2 + compose/rest/attachment.go | 26 - compose/rest/handlers/namespace.go | 161 ++++ compose/rest/namespace.go | 134 +++ compose/rest/request/namespace.go | 311 +++++++ compose/rest/request/util.go | 14 + compose/rest/router.go | 4 +- compose/types/namespace.gen.go | 67 ++ compose/types/namespace.go | 22 +- docs/compose/README.md | 788 ++++++++++++++++++ docs/messaging/README.md | 20 - system/cli/roles.go | 2 +- system/internal/repository/error.go | 2 +- 24 files changed, 2222 insertions(+), 59 deletions(-) create mode 100644 api/compose/spec/namespace.json create mode 100644 compose/db/schema/mysql/20190427210922.namespace-tbl.up.sql create mode 100644 compose/internal/repository/error.go create mode 100644 compose/internal/repository/namespace.go create mode 100644 compose/internal/service/error.go create mode 100644 compose/internal/service/namespace.go create mode 100644 compose/rest/handlers/namespace.go create mode 100644 compose/rest/namespace.go create mode 100644 compose/rest/request/namespace.go create mode 100644 compose/types/namespace.gen.go create mode 100644 docs/compose/README.md diff --git a/api/compose/spec.json b/api/compose/spec.json index 0fc5c11eb..54627907f 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -24,6 +24,165 @@ } ] }, + { + "title": "Namespaces", + "parameters": {}, + "entrypoint": "namespace", + "path": "/namespace", + "authentication": [], + "struct": [ + { + "imports": [ + "sqlxTypes github.com/jmoiron/sqlx/types", + "time" + ] + } + ], + "apis": [ + { + "name": "list", + "method": "GET", + "title": "List namespaces", + "path": "/", + "parameters": { + "get": [ + { + "type": "string", + "name": "query", + "required": false, + "title": "Search query" + }, + { + "name": "page", + "type": "uint", + "required": false, + "title": "Page number (0 based)" + }, + { + "name": "perPage", + "type": "uint", + "required": false, + "title": "Returned items per page (default 50)" + } + ] + } + }, + { + "name": "create", + "method": "POST", + "title": "Create namespace", + "path": "/", + "parameters": { + "post": [ + { + "type": "string", + "name": "name", + "required": true, + "title": "Name" + }, + { + "type": "string", + "name": "slug", + "required": true, + "title": "Slug (url path part)" + }, + { + "type": "bool", + "name": "enabled", + "required": true, + "title": "Enabled" + }, + { + "type": "sqlxTypes.JSONText", + "name": "meta", + "required": true, + "title": "Meta data" + } + ] + } + }, + { + "name": "read", + "method": "GET", + "title": "Read namespace", + "path": "/{namespaceID}", + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "ID" + } + ] + } + }, + { + "name": "update", + "method": "POST", + "title": "Update namespace", + "path": "/{namespaceID}", + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "ID" + } + ], + "post": [ + { + "type": "string", + "name": "name", + "required": true, + "title": "Name" + }, + { + "type": "string", + "name": "slug", + "required": true, + "title": "Slug (url path part)" + }, + { + "type": "bool", + "name": "enabled", + "required": true, + "title": "Enabled" + }, + { + "type": "sqlxTypes.JSONText", + "name": "meta", + "required": true, + "title": "Meta data" + }, + { + "type": "*time.Time", + "name": "updatedAt", + "required": true, + "title": "Last update (or creation) date" + } + ] + } + }, + { + "name": "delete", + "method": "DELETE", + "title": "Delete namespace", + "path": "/{namespaceID}", + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "ID" + } + ] + } + } + ] + }, { "title": "Pages", "description": "Compose module pages", @@ -374,7 +533,7 @@ }, { "title": "Records", - "description": "Compose records ", + "description": "Compose records", "entrypoint": "record", "path": "/module/{moduleID}/record", "authentication": [], diff --git a/api/compose/spec/namespace.json b/api/compose/spec/namespace.json new file mode 100644 index 000000000..3b8588e4f --- /dev/null +++ b/api/compose/spec/namespace.json @@ -0,0 +1,160 @@ +{ + "Title": "Namespaces", + "Interface": "Namespace", + "Struct": [ + { + "imports": [ + "sqlxTypes github.com/jmoiron/sqlx/types", + "time" + ] + } + ], + "Parameters": {}, + "Protocol": "", + "Authentication": [], + "Path": "/namespace", + "APIs": [ + { + "Name": "list", + "Method": "GET", + "Title": "List namespaces", + "Path": "/", + "Parameters": { + "get": [ + { + "name": "query", + "required": false, + "title": "Search query", + "type": "string" + }, + { + "name": "page", + "required": false, + "title": "Page number (0 based)", + "type": "uint" + }, + { + "name": "perPage", + "required": false, + "title": "Returned items per page (default 50)", + "type": "uint" + } + ] + } + }, + { + "Name": "create", + "Method": "POST", + "Title": "Create namespace", + "Path": "/", + "Parameters": { + "post": [ + { + "name": "name", + "required": true, + "title": "Name", + "type": "string" + }, + { + "name": "slug", + "required": true, + "title": "Slug (url path part)", + "type": "string" + }, + { + "name": "enabled", + "required": true, + "title": "Enabled", + "type": "bool" + }, + { + "name": "meta", + "required": true, + "title": "Meta data", + "type": "sqlxTypes.JSONText" + } + ] + } + }, + { + "Name": "read", + "Method": "GET", + "Title": "Read namespace", + "Path": "/{namespaceID}", + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "ID", + "type": "uint64" + } + ] + } + }, + { + "Name": "update", + "Method": "POST", + "Title": "Update namespace", + "Path": "/{namespaceID}", + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "ID", + "type": "uint64" + } + ], + "post": [ + { + "name": "name", + "required": true, + "title": "Name", + "type": "string" + }, + { + "name": "slug", + "required": true, + "title": "Slug (url path part)", + "type": "string" + }, + { + "name": "enabled", + "required": true, + "title": "Enabled", + "type": "bool" + }, + { + "name": "meta", + "required": true, + "title": "Meta data", + "type": "sqlxTypes.JSONText" + }, + { + "name": "updatedAt", + "required": true, + "title": "Last update (or creation) date", + "type": "*time.Time" + } + ] + } + }, + { + "Name": "delete", + "Method": "DELETE", + "Title": "Delete namespace", + "Path": "/{namespaceID}", + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "ID", + "type": "uint64" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/codegen.sh b/codegen.sh index c03df6a4f..59a1ffab3 100755 --- a/codegen.sh +++ b/codegen.sh @@ -22,6 +22,7 @@ function types { CGO_ENABLED=0 go build -o ./build/gen-type-set codegen/v2/type-set.go fi + ./build/gen-type-set --types Namespace --output compose/types/namespace.gen.go ./build/gen-type-set --types Attachment --output compose/types/attachment.gen.go ./build/gen-type-set --types Module --output compose/types/module.gen.go ./build/gen-type-set --types Page --output compose/types/page.gen.go diff --git a/codegen/codegen.php b/codegen/codegen.php index 9a25eb96e..e0e7c3acb 100755 --- a/codegen/codegen.php +++ b/codegen/codegen.php @@ -101,6 +101,8 @@ $parsers = array( "int" => "parseInt", "uint" => "parseUint", "bool" => "parseBool", + "time.Time" => "parseISODateWithErr", + "*time.Time" => "parseISODatePtrWithErr", "sqlxTypes.JSONText" => "parseJSONTextWithErr", ); diff --git a/compose/db/mysql/static.go b/compose/db/mysql/static.go index fc0c28726..988dd5f27 100644 --- a/compose/db/mysql/static.go +++ b/compose/db/mysql/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package mysql -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_content\n RENAME TO compose_content;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x8a)\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81G+\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x14\x00\x14\x00r\x07\x00\x00\xb3+\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_content\n RENAME TO compose_content;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x8a)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x816,\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\xf3-\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x15\x00\x15\x00\xcc\x07\x00\x00_.\x00\x00\x00\x00" diff --git a/compose/db/schema/mysql/20190427210922.namespace-tbl.up.sql b/compose/db/schema/mysql/20190427210922.namespace-tbl.up.sql new file mode 100644 index 000000000..3be6620b5 --- /dev/null +++ b/compose/db/schema/mysql/20190427210922.namespace-tbl.up.sql @@ -0,0 +1,14 @@ +CREATE TABLE `compose_namespace` ( + `id` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(64) NOT NULL COMMENT 'Name', + `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug', + `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?', + `meta` JSON NOT NULL COMMENT 'Meta data', + + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME DEFAULT NULL, + `deleted_at` DATETIME DEFAULT NULL, + + PRIMARY KEY (`id`) + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/compose/internal/repository/error.go b/compose/internal/repository/error.go new file mode 100644 index 000000000..f1d4dd37f --- /dev/null +++ b/compose/internal/repository/error.go @@ -0,0 +1,25 @@ +package repository + +import ( + "github.com/pkg/errors" +) + +type ( + repositoryError string +) + +const ( + ErrNotImplemented = repositoryError("NotImplemented") +) + +func (e repositoryError) Error() string { + return e.String() +} + +func (e repositoryError) String() string { + return "crust.compose.repository." + string(e) +} + +func (e repositoryError) new() error { + return errors.WithStack(e) +} diff --git a/compose/internal/repository/namespace.go b/compose/internal/repository/namespace.go new file mode 100644 index 000000000..b6a2af4ce --- /dev/null +++ b/compose/internal/repository/namespace.go @@ -0,0 +1,168 @@ +package repository + +import ( + "context" + "time" + + "github.com/titpetric/factory" + "gopkg.in/Masterminds/squirrel.v1" + + "github.com/crusttech/crust/compose/types" +) + +type ( + NamespaceRepository interface { + With(ctx context.Context, db *factory.DB) NamespaceRepository + + FindByID(id uint64) (*types.Namespace, error) + Find(filter types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) + Create(mod *types.Namespace) (*types.Namespace, error) + Update(mod *types.Namespace) (*types.Namespace, error) + DeleteByID(id uint64) error + } + + namespace struct { + *repository + } +) + +const ( + ErrNamespaceNotFound = repositoryError("NamespaceNotFound") +) + +func Namespace(ctx context.Context, db *factory.DB) NamespaceRepository { + return (&namespace{}).With(ctx, db) +} + +func (r namespace) table() string { + return "compose_namespace" +} + +func (r namespace) columns() []string { + return []string{ + "id", + "name", + "slug", + "enabled", + "meta", + "created_at", + "updated_at", + "deleted_at", + } +} + +func (r *namespace) With(ctx context.Context, db *factory.DB) NamespaceRepository { + return &namespace{ + repository: r.repository.With(ctx, db), + } +} + +func (r *namespace) FindByID(id uint64) (n *types.Namespace, err error) { + var ( + sql string + args []interface{} + ) + + n = &types.Namespace{} + + qb := r.query(). + Columns(r.columns()...). + Where("id = ?", id) + + if sql, args, err = qb.ToSql(); err != nil { + return + } + + if err = r.db().Get(n, sql, args...); err != nil { + return + } + + if n == nil || n.ID == 0 { + return nil, ErrNamespaceNotFound + } + + return +} + +func (r *namespace) Find(filter types.NamespaceFilter) (nn types.NamespaceSet, f types.NamespaceFilter, err error) { + var ( + sql string + args []interface{} + ) + + f = filter + + if f.PerPage > 100 { + f.PerPage = 100 + } else if f.PerPage == 0 { + f.PerPage = 50 + } + + qb := r.query() + if f.Query != "" { + q := "%" + f.Query + "%" + qb = qb.Where("name like ? OR slug like ?", q, q) + } + + { + cq := qb.Column(squirrel.Alias(squirrel.Expr("COUNT(*)"), "count")) + if sql, args, err = cq.ToSql(); err != nil { + return + } + + if err = r.db().Get(&f.Count, sql, args...); err != nil { + return + } + + if f.Count == 0 { + // No rows with this filter no need to continue + return + } + } + + if f.Page > 0 { + qb = qb.Offset(uint64(f.PerPage * f.Page)) + } + + qb = qb. + Limit(uint64(f.PerPage)). + Columns(r.columns()...). + OrderBy("id ASC") + + if sql, args, err = qb.ToSql(); err != nil { + return + } + + if err = r.db().Select(&nn, sql, args...); err != nil { + return + } + + return +} + +func (r namespace) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table()). + Where("deleted_at IS NULL") + +} + +func (r *namespace) Create(mod *types.Namespace) (*types.Namespace, error) { + mod.ID = factory.Sonyflake.NextID() + mod.CreatedAt = time.Now() + + return mod, r.db().Insert(r.table(), mod) +} + +func (r *namespace) Update(mod *types.Namespace) (*types.Namespace, error) { + now := time.Now() + mod.UpdatedAt = &now + + return mod, r.db().Replace(r.table(), mod) +} + +func (r *namespace) DeleteByID(id uint64) error { + _, err := r.db().Exec("DELETE FROM compose_namespace WHERE id=?", id) + return err +} diff --git a/compose/internal/service/error.go b/compose/internal/service/error.go new file mode 100644 index 000000000..ae7b9751a --- /dev/null +++ b/compose/internal/service/error.go @@ -0,0 +1,31 @@ +package service + +import ( + "github.com/pkg/errors" +) + +type ( + serviceError string +) + +const ( + ErrInvalidID serviceError = "InvalidID" + ErrStaleData serviceError = "StaleData" + ErrNoCreatePermissions serviceError = "NoCreatePermissions" + ErrNoReadPermissions serviceError = "NoReadPermissions" + ErrNoUpdatePermissions serviceError = "NoUpdatePermissions" + ErrNoDeletePermissions serviceError = "NoDeletePermissions" + ErrNotImplemented serviceError = "NotImplemented" +) + +func (e serviceError) Error() string { + return e.String() +} + +func (e serviceError) String() string { + return "crust.compose.service." + string(e) +} + +func (e serviceError) withStack() error { + return errors.WithStack(e) +} diff --git a/compose/internal/service/namespace.go b/compose/internal/service/namespace.go new file mode 100644 index 000000000..e544c49fd --- /dev/null +++ b/compose/internal/service/namespace.go @@ -0,0 +1,132 @@ +package service + +import ( + "context" + "time" + + "github.com/titpetric/factory" + + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" +) + +type ( + namespace struct { + db *factory.DB + ctx context.Context + + prmSvc PermissionsService + + namespaceRepo repository.NamespaceRepository + } + + NamespaceService interface { + With(ctx context.Context) NamespaceService + + FindByID(namespaceID uint64) (*types.Namespace, error) + Find(types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) + + Create(namespace *types.Namespace) (*types.Namespace, error) + Update(namespace *types.Namespace) (*types.Namespace, error) + DeleteByID(namespaceID uint64) error + } +) + +func Namespace() NamespaceService { + return (&namespace{ + prmSvc: DefaultPermissions, + }).With(context.Background()) +} + +func (svc *namespace) With(ctx context.Context) NamespaceService { + db := repository.DB(ctx) + return &namespace{ + db: db, + ctx: ctx, + + prmSvc: svc.prmSvc.With(ctx), + + namespaceRepo: repository.Namespace(ctx, db), + } +} + +func (svc *namespace) FindByID(ID uint64) (n *types.Namespace, err error) { + if ID == 0 { + return nil, ErrInvalidID.withStack() + } + + if n, err = svc.namespaceRepo.FindByID(ID); err != nil { + return + } else if !svc.prmSvc.CanReadNamespace(n) { + return nil, ErrNoReadPermissions.withStack() + } + + return +} + +func (svc *namespace) Find(filter types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) { + nn, f, err := svc.namespaceRepo.Find(filter) + if err != nil { + return nil, f, err + } + + nn, _ = nn.Filter(func(m *types.Namespace) (bool, error) { + return svc.prmSvc.CanReadNamespace(m), nil + }) + + return nn, f, nil +} + +func (svc *namespace) Create(mod *types.Namespace) (*types.Namespace, error) { + if !svc.prmSvc.CanCreateNamespace() { + return nil, ErrNoCreatePermissions.withStack() + } + + return svc.namespaceRepo.Create(mod) +} + +func (svc *namespace) Update(updated *types.Namespace) (m *types.Namespace, err error) { + m, err = svc.FindByID(updated.ID) + if err != nil { + return nil, err + } + + if isStale(updated.UpdatedAt, m.UpdatedAt, m.CreatedAt) { + return nil, ErrStaleData + } + + if !svc.prmSvc.CanUpdateNamespace(m) { + return nil, ErrNoUpdatePermissions.withStack() + } + + m.Name = updated.Name + m.Slug = updated.Slug + m.Meta = updated.Meta + m.Enabled = updated.Enabled + + return svc.namespaceRepo.Update(m) +} + +func (svc *namespace) DeleteByID(ID uint64) error { + if m, err := svc.namespaceRepo.FindByID(ID); err != nil { + return err + } else if !svc.prmSvc.CanDeleteNamespace(m) { + return ErrNoDeletePermissions.withStack() + } + + return svc.namespaceRepo.DeleteByID(ID) +} + +// Data is stale when new date does not match updatedAt or createdAt (before first update) +func isStale(new *time.Time, updatedAt *time.Time, createdAt time.Time) bool { + + if new == nil { + return false + } + + if updatedAt != nil { + return !new.Equal(*updatedAt) + } + + return new.Equal(createdAt) +} diff --git a/compose/internal/service/permissions.go b/compose/internal/service/permissions.go index dcec64e95..46a5a10ae 100644 --- a/compose/internal/service/permissions.go +++ b/compose/internal/service/permissions.go @@ -27,7 +27,10 @@ type ( Effective() (ee []effectivePermission, err error) CanAccess() bool - CanCreateNamspace() bool + CanCreateNamespace() bool + CanReadNamespace(r permissionResource) bool + CanUpdateNamespace(r permissionResource) bool + CanDeleteNamespace(r permissionResource) bool CanCreateModule(r permissionResource) bool CanReadModule(r permissionResource) bool CanUpdateModule(r permissionResource) bool @@ -95,7 +98,7 @@ func (p *permissions) Effective() (ee []effectivePermission, err error) { ee = append(ee, ep("compose", "access", p.CanAccess())) ee = append(ee, ep("compose", "grant", p.CanGrant())) - ee = append(ee, ep("compose", "namespace.create", p.CanCreateNamspace())) + ee = append(ee, ep("compose", "namespace.create", p.CanCreateNamespace())) ee = append(ee, ep("compose:namespace:crm", "module.create", p.CanCreateModule(crmNamespace()))) ee = append(ee, ep("compose:namespace:crm", "chart.create", p.CanCreateChart(crmNamespace()))) @@ -113,13 +116,24 @@ func (p *permissions) CanGrant() bool { return p.checkAccess(types.PermissionResource, "grant") } -func (p *permissions) CanCreateNamspace() bool { +func (p *permissions) CanCreateNamespace() bool { return p.checkAccess(types.PermissionResource, "namespace.create") } -func (p *permissions) CanCreateModule(ns permissionResource) bool { - // @todo move to func args when namespaces are implemented - return p.checkAccess(ns, "module.create") +func (p *permissions) CanReadNamespace(r permissionResource) bool { + return p.checkAccess(r, "read", p.allow()) +} + +func (p *permissions) CanUpdateNamespace(r permissionResource) bool { + return p.checkAccess(r, "update") +} + +func (p *permissions) CanDeleteNamespace(r permissionResource) bool { + return p.checkAccess(r, "delete") +} + +func (p *permissions) CanCreateModule(r permissionResource) bool { + return p.checkAccess(r, "module.create") } func (p *permissions) CanReadModule(r permissionResource) bool { @@ -206,3 +220,9 @@ func (p *permissions) checkAccess(resource permissionResource, operation string, } return false } + +func (p permissions) allow() func() internalRules.Access { + return func() internalRules.Access { + return internalRules.Allow + } +} diff --git a/compose/internal/service/service.go b/compose/internal/service/service.go index 5cbb01218..7a1416380 100644 --- a/compose/internal/service/service.go +++ b/compose/internal/service/service.go @@ -19,6 +19,7 @@ var ( DefaultNotification NotificationService DefaultPermissions PermissionsService DefaultAttachment AttachmentService + DefaultNamespace NamespaceService ) func Init() error { @@ -35,6 +36,7 @@ func Init() error { DefaultChart = Chart() DefaultNotification = Notification() DefaultAttachment = Attachment(fs) + DefaultNamespace = Namespace() return nil } diff --git a/compose/rest/attachment.go b/compose/rest/attachment.go index 7f9babf0a..c150b2f07 100644 --- a/compose/rest/attachment.go +++ b/compose/rest/attachment.go @@ -30,12 +30,6 @@ type ( UpdatedAt *time.Time `json:"updatedAt,omitempty"` } - file struct { - *types.Attachment - content io.ReadSeeker - download bool - } - Attachment struct { attachment service.AttachmentService } @@ -176,23 +170,3 @@ func makeAttachmentPayload(a *types.Attachment, userID uint64) *attachmentPayloa UpdatedAt: a.UpdatedAt, } } - -func (f *file) Download() bool { - return f.download -} - -func (f *file) Name() string { - return f.Attachment.Name -} - -func (f *file) ModTime() time.Time { - return f.Attachment.CreatedAt -} - -func (f *file) Content() io.ReadSeeker { - return f.content -} - -func (f *file) Valid() bool { - return f.content != nil -} diff --git a/compose/rest/handlers/namespace.go b/compose/rest/handlers/namespace.go new file mode 100644 index 000000000..4594d6a7b --- /dev/null +++ b/compose/rest/handlers/namespace.go @@ -0,0 +1,161 @@ +package handlers + +/* + Hello! This file is auto-generated from `docs/src/spec.json`. + + For development: + In order to update the generated files, edit this file under the location, + add your struct fields, imports, API definitions and whatever you want, and: + + 1. run [spec](https://github.com/titpetric/spec) in the same folder, + 2. run `./_gen.php` in this folder. + + You may edit `namespace.go`, `namespace.util.go` or `namespace_test.go` to + implement your API calls, helper functions and tests. The file `namespace.go` + is only generated the first time, and will not be overwritten if it exists. +*/ + +import ( + "context" + + "net/http" + + "github.com/go-chi/chi" + "github.com/titpetric/factory/resputil" + + "github.com/crusttech/crust/compose/rest/request" +) + +// Internal API interface +type NamespaceAPI interface { + List(context.Context, *request.NamespaceList) (interface{}, error) + Create(context.Context, *request.NamespaceCreate) (interface{}, error) + Read(context.Context, *request.NamespaceRead) (interface{}, error) + Update(context.Context, *request.NamespaceUpdate) (interface{}, error) + Delete(context.Context, *request.NamespaceDelete) (interface{}, error) +} + +// HTTP API interface +type Namespace struct { + List func(http.ResponseWriter, *http.Request) + Create func(http.ResponseWriter, *http.Request) + Read func(http.ResponseWriter, *http.Request) + Update func(http.ResponseWriter, *http.Request) + Delete func(http.ResponseWriter, *http.Request) +} + +func NewNamespace(nh NamespaceAPI) *Namespace { + return &Namespace{ + List: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceList() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.List(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + Create: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceCreate() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.Create(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + Read: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceRead() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.Read(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + Update: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceUpdate() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.Update(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + Delete: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceDelete() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.Delete(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + } +} + +func (nh *Namespace) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Get("/namespace/", nh.List) + r.Post("/namespace/", nh.Create) + r.Get("/namespace/{namespaceID}", nh.Read) + r.Post("/namespace/{namespaceID}", nh.Update) + r.Delete("/namespace/{namespaceID}", nh.Delete) + }) +} diff --git a/compose/rest/namespace.go b/compose/rest/namespace.go new file mode 100644 index 000000000..2c693756d --- /dev/null +++ b/compose/rest/namespace.go @@ -0,0 +1,134 @@ +package rest + +import ( + "context" + + "github.com/titpetric/factory/resputil" + + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" + "github.com/crusttech/crust/compose/types" +) + +type ( + namespacePayload struct { + *types.Namespace + + CanUpdateNamespace bool `json:"canUpdateNamespace"` + CanDeleteNamespace bool `json:"canDeleteNamespace"` + CanCreateModule bool `json:"canCreateModule"` + CanCreateChart bool `json:"canCreateChart"` + CanCreateTrigger bool `json:"canCreateTrigger"` + CanCreatePage bool `json:"canCreatePage"` + } + + namespaceSetPayload struct { + Filter types.NamespaceFilter `json:"filter"` + Set []*namespacePayload `json:"set"` + } +) + +type Namespace struct { + namespace service.NamespaceService + permissions service.PermissionsService +} + +func (Namespace) New() *Namespace { + return &Namespace{ + namespace: service.DefaultNamespace, + permissions: service.DefaultPermissions, + } +} + +func (ctrl Namespace) List(ctx context.Context, r *request.NamespaceList) (interface{}, error) { + f := types.NamespaceFilter{ + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, + } + + nn, filter, err := ctrl.namespace.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, nn, filter, err) +} + +func (ctrl Namespace) Create(ctx context.Context, r *request.NamespaceCreate) (interface{}, error) { + var err error + ns := &types.Namespace{ + Name: r.Name, + Slug: r.Slug, + Meta: r.Meta, + Enabled: r.Enabled, + } + + ns, err = ctrl.namespace.With(ctx).Create(ns) + return ctrl.makePayload(ctx, ns, err) +} + +func (ctrl Namespace) Read(ctx context.Context, r *request.NamespaceRead) (interface{}, error) { + ns, err := ctrl.namespace.With(ctx).FindByID(r.NamespaceID) + return ctrl.makePayload(ctx, ns, err) +} + +func (ctrl Namespace) Update(ctx context.Context, r *request.NamespaceUpdate) (interface{}, error) { + var ( + ns = &types.Namespace{} + err error + ) + + ns.ID = r.NamespaceID + ns.Name = r.Name + ns.Slug = r.Slug + ns.Meta = r.Meta + ns.Enabled = r.Enabled + ns.UpdatedAt = r.UpdatedAt + + ns, err = ctrl.namespace.With(ctx).Update(ns) + return ctrl.makePayload(ctx, ns, err) +} + +func (ctrl Namespace) Delete(ctx context.Context, r *request.NamespaceDelete) (interface{}, error) { + _, err := ctrl.namespace.With(ctx).FindByID(r.NamespaceID) + if err != nil { + return nil, err + } + + err = ctrl.namespace.With(ctx).DeleteByID(r.NamespaceID) + if err != nil { + return nil, err + } else { + return resputil.Success("deleted"), nil + } +} + +func (ctrl Namespace) makePayload(ctx context.Context, ns *types.Namespace, err error) (*namespacePayload, error) { + if err != nil { + return nil, err + } + + perm := ctrl.permissions.With(ctx) + + return &namespacePayload{ + Namespace: ns, + + CanUpdateNamespace: perm.CanUpdateNamespace(ns), + CanDeleteNamespace: perm.CanDeleteNamespace(ns), + CanCreateModule: perm.CanCreateModule(ns), + CanCreateChart: perm.CanCreateChart(ns), + CanCreateTrigger: perm.CanCreateTrigger(ns), + CanCreatePage: perm.CanCreatePage(ns), + }, nil +} + +func (ctrl Namespace) makeFilterPayload(ctx context.Context, nn types.NamespaceSet, f types.NamespaceFilter, err error) (*namespaceSetPayload, error) { + if err != nil { + return nil, err + } + + nsp := &namespaceSetPayload{Filter: f, Set: make([]*namespacePayload, len(nn))} + + for i := range nn { + nsp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + } + + return nsp, nil +} diff --git a/compose/rest/request/namespace.go b/compose/rest/request/namespace.go new file mode 100644 index 000000000..125f6d337 --- /dev/null +++ b/compose/rest/request/namespace.go @@ -0,0 +1,311 @@ +package request + +/* + Hello! This file is auto-generated from `docs/src/spec.json`. + + For development: + In order to update the generated files, edit this file under the location, + add your struct fields, imports, API definitions and whatever you want, and: + + 1. run [spec](https://github.com/titpetric/spec) in the same folder, + 2. run `./_gen.php` in this folder. + + You may edit `namespace.go`, `namespace.util.go` or `namespace_test.go` to + implement your API calls, helper functions and tests. The file `namespace.go` + is only generated the first time, and will not be overwritten if it exists. +*/ + +import ( + "io" + "strings" + + "encoding/json" + "mime/multipart" + "net/http" + + "github.com/go-chi/chi" + "github.com/pkg/errors" + + sqlxTypes "github.com/jmoiron/sqlx/types" + "time" +) + +var _ = chi.URLParam +var _ = multipart.FileHeader{} + +// Namespace list request parameters +type NamespaceList struct { + Query string + Page uint + PerPage uint +} + +func NewNamespaceList() *NamespaceList { + return &NamespaceList{} +} + +func (nReq *NamespaceList) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + if val, ok := get["query"]; ok { + + nReq.Query = val + } + if val, ok := get["page"]; ok { + + nReq.Page = parseUint(val) + } + if val, ok := get["perPage"]; ok { + + nReq.PerPage = parseUint(val) + } + + return err +} + +var _ RequestFiller = NewNamespaceList() + +// Namespace create request parameters +type NamespaceCreate struct { + Name string + Slug string + Enabled bool + Meta sqlxTypes.JSONText +} + +func NewNamespaceCreate() *NamespaceCreate { + return &NamespaceCreate{} +} + +func (nReq *NamespaceCreate) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + if val, ok := post["name"]; ok { + + nReq.Name = val + } + if val, ok := post["slug"]; ok { + + nReq.Slug = val + } + if val, ok := post["enabled"]; ok { + + nReq.Enabled = parseBool(val) + } + if val, ok := post["meta"]; ok { + + if nReq.Meta, err = parseJSONTextWithErr(val); err != nil { + return err + } + } + + return err +} + +var _ RequestFiller = NewNamespaceCreate() + +// Namespace read request parameters +type NamespaceRead struct { + NamespaceID uint64 `json:",string"` +} + +func NewNamespaceRead() *NamespaceRead { + return &NamespaceRead{} +} + +func (nReq *NamespaceRead) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + nReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) + + return err +} + +var _ RequestFiller = NewNamespaceRead() + +// Namespace update request parameters +type NamespaceUpdate struct { + NamespaceID uint64 `json:",string"` + Name string + Slug string + Enabled bool + Meta sqlxTypes.JSONText + UpdatedAt *time.Time +} + +func NewNamespaceUpdate() *NamespaceUpdate { + return &NamespaceUpdate{} +} + +func (nReq *NamespaceUpdate) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + nReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) + if val, ok := post["name"]; ok { + + nReq.Name = val + } + if val, ok := post["slug"]; ok { + + nReq.Slug = val + } + if val, ok := post["enabled"]; ok { + + nReq.Enabled = parseBool(val) + } + if val, ok := post["meta"]; ok { + + if nReq.Meta, err = parseJSONTextWithErr(val); err != nil { + return err + } + } + if val, ok := post["updatedAt"]; ok { + + if nReq.UpdatedAt, err = parseISODatePtrWithErr(val); err != nil { + return err + } + } + + return err +} + +var _ RequestFiller = NewNamespaceUpdate() + +// Namespace delete request parameters +type NamespaceDelete struct { + NamespaceID uint64 `json:",string"` +} + +func NewNamespaceDelete() *NamespaceDelete { + return &NamespaceDelete{} +} + +func (nReq *NamespaceDelete) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + nReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) + + return err +} + +var _ RequestFiller = NewNamespaceDelete() diff --git a/compose/rest/request/util.go b/compose/rest/request/util.go index 4a1d7096e..fc9a011d5 100644 --- a/compose/rest/request/util.go +++ b/compose/rest/request/util.go @@ -4,6 +4,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/jmoiron/sqlx/types" "github.com/pkg/errors" @@ -17,6 +18,19 @@ func parseJSONTextWithErr(s string) (types.JSONText, error) { return *result, err } +func parseISODateWithErr(s string) (time.Time, error) { + return time.Parse(time.RFC3339, s) +} + +func parseISODatePtrWithErr(s string) (*time.Time, error) { + t, err := parseISODateWithErr(s) + if err != nil { + return nil, err + } + + return &t, nil +} + // parseInt parses a string to int func parseInt(s string) int { if s == "" { diff --git a/compose/rest/router.go b/compose/rest/router.go index a0918fe54..ebbbcad76 100644 --- a/compose/rest/router.go +++ b/compose/rest/router.go @@ -9,6 +9,7 @@ import ( func MountRoutes() func(chi.Router) { var ( + namespace = Namespace{}.New() module = Module{}.New() record = Record{}.New() page = Page{}.New() @@ -16,8 +17,6 @@ func MountRoutes() func(chi.Router) { trigger = Trigger{}.New() notification = Notification{}.New() attachment = Attachment{}.New() - // pageAttachment = PageAttachment{}.New() - // recordAttachment = RecordAttachment{}.New() ) // Initialize handlers & controllers. @@ -32,6 +31,7 @@ func MountRoutes() func(chi.Router) { r.Use(auth.MiddlewareValidOnly) r.Use(middlewareAllowedAccess) + handlers.NewNamespace(namespace).MountRoutes(r) handlers.NewPage(page).MountRoutes(r) handlers.NewModule(module).MountRoutes(r) handlers.NewRecord(record).MountRoutes(r) diff --git a/compose/types/namespace.gen.go b/compose/types/namespace.gen.go new file mode 100644 index 000000000..55f87f097 --- /dev/null +++ b/compose/types/namespace.gen.go @@ -0,0 +1,67 @@ +package types + +// Hello! This file is auto-generated. + +type ( + + // NamespaceSet slice of Namespace + // + // This type is auto-generated. + NamespaceSet []*Namespace +) + +// Walk iterates through every slice item and calls w(Namespace) err +// +// This function is auto-generated. +func (set NamespaceSet) Walk(w func(*Namespace) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(Namespace) (bool, err) and return filtered slice +// +// This function is auto-generated. +func (set NamespaceSet) Filter(f func(*Namespace) (bool, error)) (out NamespaceSet, err error) { + var ok bool + out = NamespaceSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// FindByID finds items from slice by its ID property +// +// This function is auto-generated. +func (set NamespaceSet) FindByID(ID uint64) *Namespace { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// IDs returns a slice of uint64s from all items in the set +// +// This function is auto-generated. +func (set NamespaceSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} diff --git a/compose/types/namespace.go b/compose/types/namespace.go index 3a60ecd3f..ef1390ef7 100644 --- a/compose/types/namespace.go +++ b/compose/types/namespace.go @@ -1,12 +1,32 @@ package types import ( + "time" + + "github.com/jmoiron/sqlx/types" + "github.com/crusttech/crust/internal/rules" ) type ( Namespace struct { - ID uint64 `json:"id,string" db:"id"` + ID uint64 `json:"namespaceID,string" db:"id"` + Name string `json:"name" db:"name"` + Slug string `json:"slug" db:"slug"` + Enabled bool `json:"enabled" db:"enabled"` + Meta types.JSONText `json:"meta" db:"meta"` + + CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"` + UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at"` + DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"` + } + + NamespaceFilter struct { + Query string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + Sort string `json:"sort"` + Count uint `json:"count"` } ) diff --git a/docs/compose/README.md b/docs/compose/README.md new file mode 100644 index 000000000..c9551c3d1 --- /dev/null +++ b/docs/compose/README.md @@ -0,0 +1,788 @@ +# Attachments + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/attachment/{kind}/` | List, filter all page attachments | +| `GET` | `/attachment/{kind}/{attachmentID}` | Attachment details | +| `GET` | `/attachment/{kind}/{attachmentID}/original/{name}` | Serves attached file | +| `GET` | `/attachment/{kind}/{attachmentID}/preview.{ext}` | Serves preview of an attached file | + +## List, filter all page attachments + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/attachment/{kind}/` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | GET | Filter attachments by page ID | N/A | NO | +| moduleID | uint64 | GET | Filter attachments by mnodule ID | N/A | NO | +| recordID | uint64 | GET | Filter attachments by record ID | N/A | NO | +| fieldName | string | GET | Filter attachments by field name | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | +| sign | string | GET | Signature | N/A | YES | +| userID | uint64 | GET | User ID | N/A | YES | +| kind | string | PATH | Attachment kind | N/A | YES | + +## Attachment details + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/attachment/{kind}/{attachmentID}` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | +| kind | string | PATH | Attachment kind | N/A | YES | +| sign | string | GET | Signature | N/A | YES | +| userID | uint64 | GET | User ID | N/A | YES | + +## Serves attached file + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/attachment/{kind}/{attachmentID}/original/{name}` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| download | bool | GET | Force file download | N/A | NO | +| sign | string | GET | Signature | N/A | YES | +| userID | uint64 | GET | User ID | N/A | YES | +| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | +| name | string | PATH | File name | N/A | YES | +| kind | string | PATH | Attachment kind | N/A | YES | + +## Serves preview of an attached file + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/attachment/{kind}/{attachmentID}/preview.{ext}` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | +| ext | string | PATH | Preview extension/format | N/A | YES | +| kind | string | PATH | Attachment kind | N/A | YES | +| sign | string | GET | Signature | N/A | YES | +| userID | uint64 | GET | User ID | N/A | YES | + +--- + + + + +# Charts + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/chart/` | List/read charts from module section | +| `POST` | `/chart/` | List/read charts from module section | +| `GET` | `/chart/{chartID}` | Read charts by ID from module section | +| `POST` | `/chart/{chartID}` | Add/update charts in module section | +| `DELETE` | `/chart/{chartID}` | Delete chart | + +## List/read charts from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | + +## List/read charts from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| config | sqlxTypes.JSONText | POST | Chart JSON | N/A | YES | +| name | string | POST | Chart name | N/A | YES | + +## Read charts by ID from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/{chartID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| chartID | uint64 | PATH | Chart ID | N/A | YES | + +## Add/update charts in module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/{chartID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| chartID | uint64 | PATH | Chart ID | N/A | YES | +| config | sqlxTypes.JSONText | POST | Chart JSON | N/A | YES | +| name | string | POST | Chart name | N/A | YES | + +## Delete chart + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/{chartID}` | HTTP/S | DELETE | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| chartID | uint64 | PATH | Chart ID | N/A | YES | + +--- + + + + +# Modules + +Compose module definitions + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/module/` | List modules | +| `POST` | `/module/` | Create module | +| `GET` | `/module/{moduleID}` | Read module | +| `POST` | `/module/{moduleID}` | Update module | +| `DELETE` | `/module/{moduleID}` | Delete module | + +## List modules + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| query | string | GET | Search query | N/A | NO | + +## Create module + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| name | string | POST | Module Name | N/A | YES | +| fields | types.ModuleFieldSet | POST | Fields JSON | N/A | YES | +| meta | sqlxTypes.JSONText | POST | Module meta data | N/A | YES | + +## Read module + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Update module + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | PATH | Module ID | N/A | YES | +| name | string | POST | Module Name | N/A | YES | +| fields | types.ModuleFieldSet | POST | Fields JSON | N/A | YES | +| meta | sqlxTypes.JSONText | POST | Module meta data | N/A | YES | + +## Delete module + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}` | HTTP/S | DELETE | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +--- + + + + +# Namespaces + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/namespace/` | List namespaces | +| `POST` | `/namespace/` | Create namespace | +| `GET` | `/namespace/{namespaceID}` | Read namespace | +| `POST` | `/namespace/{namespaceID}` | Update namespace | +| `DELETE` | `/namespace/{namespaceID}` | Delete namespace | + +## List namespaces + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| query | string | GET | Search query | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | + +## Create namespace + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| name | string | POST | Name | N/A | YES | +| slug | string | POST | Slug (url path part) | N/A | YES | +| enabled | bool | POST | Enabled | N/A | YES | +| meta | sqlxTypes.JSONText | POST | Meta data | N/A | YES | + +## Read namespace + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/{namespaceID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| namespaceID | uint64 | PATH | ID | N/A | YES | + +## Update namespace + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/{namespaceID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| namespaceID | uint64 | PATH | ID | N/A | YES | +| name | string | POST | Name | N/A | YES | +| slug | string | POST | Slug (url path part) | N/A | YES | +| enabled | bool | POST | Enabled | N/A | YES | +| meta | sqlxTypes.JSONText | POST | Meta data | N/A | YES | +| updatedAt | *time.Time | POST | Last update (or creation) date | N/A | YES | + +## Delete namespace + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/{namespaceID}` | HTTP/S | DELETE | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| namespaceID | uint64 | PATH | ID | N/A | YES | + +--- + + + + +# Notifications + +Compose Notifications + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `POST` | `/notification/email` | Send email from the Compose | + +## Send email from the Compose + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/notification/email` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| to | []string | POST | Email addresses or Crust user IDs | N/A | YES | +| cc | []string | POST | Email addresses or Crust user IDs | N/A | NO | +| replyTo | string | POST | Crust user ID or email address in reply-to field | N/A | NO | +| subject | string | POST | Email subject | N/A | NO | +| content | sqlxTypes.JSONText | POST | Message content | N/A | YES | + +--- + + + + +# Pages + +Compose module pages + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/page/` | List available pages | +| `POST` | `/page/` | Create page | +| `GET` | `/page/{pageID}` | Get page details | +| `GET` | `/page/tree` | Get page all (non-record) pages, hierarchically | +| `POST` | `/page/{pageID}` | Update page | +| `POST` | `/page/{selfID}/reorder` | Reorder pages | +| `Delete` | `/page/{pageID}` | Delete page | +| `POST` | `/page/{pageID}/attachment` | Uploads attachment to page | + +## List available pages + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| selfID | uint64 | GET | Parent page ID | N/A | NO | + +## Create page + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| selfID | uint64 | POST | Parent Page ID | N/A | NO | +| moduleID | uint64 | POST | Module ID | N/A | NO | +| title | string | POST | Title | N/A | YES | +| description | string | POST | Description | N/A | NO | +| visible | bool | POST | Visible in navigation | N/A | NO | +| blocks | sqlxTypes.JSONText | POST | Blocks JSON | N/A | YES | + +## Get page details + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{pageID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | PATH | Page ID | N/A | YES | + +## Get page all (non-record) pages, hierarchically + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/tree` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | + +## Update page + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{pageID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | PATH | Page ID | N/A | YES | +| selfID | uint64 | POST | Parent Page ID | N/A | NO | +| moduleID | uint64 | POST | Module ID (optional) | N/A | NO | +| title | string | POST | Title | N/A | YES | +| description | string | POST | Description | N/A | NO | +| visible | bool | POST | Visible in navigation | N/A | NO | +| blocks | sqlxTypes.JSONText | POST | Blocks JSON | N/A | YES | + +## Reorder pages + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{selfID}/reorder` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| selfID | uint64 | PATH | Parent page ID | N/A | YES | +| pageIDs | []string | POST | Page ID order | N/A | YES | + +## Delete page + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{pageID}` | HTTP/S | Delete | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | PATH | Page ID | N/A | YES | + +## Uploads attachment to page + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{pageID}/attachment` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | PATH | Page ID | N/A | YES | +| upload | *multipart.FileHeader | POST | File to upload | N/A | YES | + +--- + + + + +# Permissions + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/permissions/effective` | Effective rules for current user | + +## Effective rules for current user + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/permissions/effective` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| resource | string | GET | Show only rules for a specific resource | N/A | NO | + +--- + + + + +# Records + +Compose records + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/module/{moduleID}/record/report` | Generates report from module records | +| `GET` | `/module/{moduleID}/record/` | List/read records from module section | +| `POST` | `/module/{moduleID}/record/` | Create record in module section | +| `GET` | `/module/{moduleID}/record/{recordID}` | Read records by ID from module section | +| `POST` | `/module/{moduleID}/record/{recordID}` | Update records in module section | +| `DELETE` | `/module/{moduleID}/record/{recordID}` | Delete record row from module section | +| `POST` | `/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | Uploads attachment and validates it against record field requirements | + +## Generates report from module records + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/report` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| metrics | string | GET | Metrics (eg: 'SUM(money), MAX(calls)') | N/A | NO | +| dimensions | string | GET | Dimensions (eg: 'DATE(foo), status') | N/A | YES | +| filter | string | GET | Filter (eg: 'DATE(foo) > 2010') | N/A | NO | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## List/read records from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| filter | string | GET | Filtering condition | N/A | NO | +| page | int | GET | Page number (0 based) | N/A | NO | +| perPage | int | GET | Returned items per page (default 50) | N/A | NO | +| sort | string | GET | Sort field (default id desc) | N/A | NO | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Create record in module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| values | types.RecordValueSet | POST | Record values | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Read records by ID from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/{recordID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| recordID | uint64 | PATH | Record ID | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Update records in module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/{recordID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| recordID | uint64 | PATH | Record ID | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | +| values | types.RecordValueSet | POST | Record values | N/A | YES | + +## Delete record row from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/{recordID}` | HTTP/S | DELETE | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| recordID | uint64 | PATH | Record ID | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Uploads attachment and validates it against record field requirements + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| recordID | uint64 | PATH | Record ID | N/A | YES | +| fieldName | string | PATH | Field name | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | +| upload | *multipart.FileHeader | POST | File to upload | N/A | YES | + +--- + + + + +# Triggers + +Compose Triggers + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/trigger/` | List available triggers | +| `POST` | `/trigger/` | Create trigger | +| `GET` | `/trigger/{triggerID}` | Get trigger details | +| `POST` | `/trigger/{triggerID}` | Update trigger | +| `Delete` | `/trigger/{triggerID}` | Delete trigger | + +## List available triggers + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | GET | Filter triggers by module | N/A | NO | + +## Create trigger + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | POST | Module ID | N/A | NO | +| name | string | POST | Name | N/A | YES | +| actions | []string | POST | Actions that trigger this trigger | N/A | NO | +| enabled | bool | POST | Enabled | N/A | NO | +| source | string | POST | Trigger source code | N/A | NO | + +## Get trigger details + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/{triggerID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| triggerID | uint64 | PATH | Trigger ID | N/A | YES | + +## Update trigger + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/{triggerID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| triggerID | uint64 | PATH | Trigger ID | N/A | YES | +| moduleID | uint64 | POST | Module ID | N/A | NO | +| name | string | POST | Name | N/A | YES | +| actions | []string | POST | Actions that trigger this trigger | N/A | NO | +| enabled | bool | POST | Enabled | N/A | NO | +| source | string | POST | Trigger source code | N/A | NO | + +## Delete trigger + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/{triggerID}` | HTTP/S | Delete | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| triggerID | uint64 | PATH | Trigger ID | N/A | YES | + +--- \ No newline at end of file diff --git a/docs/messaging/README.md b/docs/messaging/README.md index 966cb8dea..5ccaac3c9 100644 --- a/docs/messaging/README.md +++ b/docs/messaging/README.md @@ -304,26 +304,6 @@ A channel is a representation of a sequence of messages. It has meta data like c # Messages -Messages represent individual messages in the chat system. Messages are typed, indicating the event which triggered the message. - -Currently expected message types are: - -| Name | Description | -| ---- | ----------- | -| CREATE | The first message when the channel is created | -| TOPIC | A member changed the topic of the channel | -| RENAME | A member renamed the channel | -| MESSAGE | A member posted a message to the channel | -| FILE | A member uploaded a file to the channel | - -The following event types may be sent with a message event: - -| Name | Description | -| ---- | ----------- | -| CREATED | A message has been created on a channel | -| EDITED | A message has been edited by the sender | -| REMOVED | A message has been removed by the sender | - | Method | Endpoint | Purpose | | ------ | -------- | ------- | | `POST` | `/channels/{channelID}/messages/` | Post new message to the channel | diff --git a/system/cli/roles.go b/system/cli/roles.go index 3e2db0b24..a1e550525 100644 --- a/system/cli/roles.go +++ b/system/cli/roles.go @@ -70,10 +70,10 @@ func rolesResetCmd(ctx context.Context, db *factory.DB) func(cmd *cobra.Command, {2, "compose", "namespace.create", 2}, {2, "compose", "access", 2}, {2, "compose", "grant", 2}, - {2, "compose:namespace:*", "page.create", 2}, {2, "compose:namespace:*", "read", 2}, {2, "compose:namespace:*", "update", 2}, {2, "compose:namespace:*", "delete", 2}, + {2, "compose:namespace:*", "page.create", 2}, {2, "compose:namespace:*", "module.create", 2}, {2, "compose:namespace:*", "chart.create", 2}, {2, "compose:namespace:*", "trigger.create", 2}, diff --git a/system/internal/repository/error.go b/system/internal/repository/error.go index 309b60aee..2cc825428 100644 --- a/system/internal/repository/error.go +++ b/system/internal/repository/error.go @@ -14,5 +14,5 @@ func (e repositoryError) Error() string { } func (e repositoryError) String() string { - return "crust.auth.repository." + string(e) + return "crust.system.repository." + string(e) } From d10a89b31a523e8b23d7b6229efe859a10d1598a Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sun, 28 Apr 2019 09:08:11 +0200 Subject: [PATCH 03/14] Add /namespace/{namespaceID} prefix to compose routes Attachments, charts, modules, records, pages, triggers can now be accessed under /namespace/{namespaceID}/... --- api/compose/spec.json | 64 ++++++++++- api/compose/spec/attachment.json | 8 +- api/compose/spec/chart.json | 13 ++- api/compose/spec/module.json | 13 ++- api/compose/spec/page.json | 13 ++- api/compose/spec/record.json | 8 +- api/compose/spec/trigger.json | 13 ++- compose/rest/attachment.go | 9 +- compose/rest/handlers/attachment.go | 8 +- compose/rest/handlers/chart.go | 10 +- compose/rest/handlers/module.go | 10 +- compose/rest/handlers/page.go | 16 +-- compose/rest/handlers/record.go | 14 +-- compose/rest/handlers/trigger.go | 10 +- compose/rest/request/attachment.go | 26 +++-- compose/rest/request/chart.go | 25 ++-- compose/rest/request/module.go | 30 +++-- compose/rest/request/page.go | 31 +++-- compose/rest/request/record.go | 58 ++++++---- compose/rest/request/trigger.go | 38 ++++--- docs/compose/README.md | 170 +++++++++++++++++----------- 21 files changed, 396 insertions(+), 191 deletions(-) diff --git a/api/compose/spec.json b/api/compose/spec.json index 54627907f..35839d4b9 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -187,7 +187,7 @@ "title": "Pages", "description": "Compose module pages", "entrypoint": "page", - "path": "/page", + "path": "/namespace/{namespaceID}/page", "authentication": [], "struct": [ { @@ -196,6 +196,16 @@ ] } ], + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "Namespace ID" + } + ] + }, "apis": [ { "name": "list", @@ -406,8 +416,18 @@ "title": "Modules", "description": "Compose module definitions", "entrypoint": "module", - "path": "/module", + "path": "/namespace/{namespaceID}/module", "authentication": [], + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "Namespace ID" + } + ] + }, "struct": [ { "imports": [ @@ -535,7 +555,7 @@ "title": "Records", "description": "Compose records", "entrypoint": "record", - "path": "/module/{moduleID}/record", + "path": "/namespace/{namespaceID}/module/{moduleID}/record", "authentication": [], "struct": [ { @@ -546,6 +566,12 @@ ], "parameters": { "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "Namespace ID" + }, { "type": "uint64", "name": "moduleID", @@ -723,7 +749,7 @@ }, { "title": "Charts", - "path": "/chart", + "path": "/namespace/{namespaceID}/chart", "entrypoint": "chart", "authentication": [], "struct": [ @@ -733,6 +759,16 @@ ] } ], + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "Namespace ID" + } + ] + }, "apis": [ { "name": "list", @@ -830,8 +866,18 @@ "title": "Triggers", "description": "Compose Triggers", "entrypoint": "trigger", - "path": "/trigger", + "path": "/namespace/{namespaceID}/trigger", "authentication": [], + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "Namespace ID" + } + ] + }, "apis": [ { "name": "list", @@ -1029,7 +1075,7 @@ }, { "title": "Attachments", - "path": "/attachment/{kind}", + "path": "/namespace/{namespaceID}/attachment/{kind}", "entrypoint": "attachment", "authentication": [ "Client ID", @@ -1042,6 +1088,12 @@ "type": "string", "required": true, "title": "Attachment kind" + }, + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "Namespace ID" } ], "get": [ diff --git a/api/compose/spec/attachment.json b/api/compose/spec/attachment.json index 5679850d8..80cf0d9f0 100644 --- a/api/compose/spec/attachment.json +++ b/api/compose/spec/attachment.json @@ -23,6 +23,12 @@ "required": true, "title": "Attachment kind", "type": "string" + }, + { + "name": "namespaceID", + "required": true, + "title": "Namespace ID", + "type": "uint64" } ] }, @@ -31,7 +37,7 @@ "Client ID", "Session ID" ], - "Path": "/attachment/{kind}", + "Path": "/namespace/{namespaceID}/attachment/{kind}", "APIs": [ { "Name": "list", diff --git a/api/compose/spec/chart.json b/api/compose/spec/chart.json index bf1cb21fb..efa46aded 100644 --- a/api/compose/spec/chart.json +++ b/api/compose/spec/chart.json @@ -8,10 +8,19 @@ ] } ], - "Parameters": null, + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "Namespace ID", + "type": "uint64" + } + ] + }, "Protocol": "", "Authentication": [], - "Path": "/chart", + "Path": "/namespace/{namespaceID}/chart", "APIs": [ { "Name": "list", diff --git a/api/compose/spec/module.json b/api/compose/spec/module.json index 9aa371166..1ca51e4d8 100644 --- a/api/compose/spec/module.json +++ b/api/compose/spec/module.json @@ -10,10 +10,19 @@ ] } ], - "Parameters": null, + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "Namespace ID", + "type": "uint64" + } + ] + }, "Protocol": "", "Authentication": [], - "Path": "/module", + "Path": "/namespace/{namespaceID}/module", "APIs": [ { "Name": "list", diff --git a/api/compose/spec/page.json b/api/compose/spec/page.json index 54a9093d7..19f9bb502 100644 --- a/api/compose/spec/page.json +++ b/api/compose/spec/page.json @@ -9,10 +9,19 @@ ] } ], - "Parameters": null, + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "Namespace ID", + "type": "uint64" + } + ] + }, "Protocol": "", "Authentication": [], - "Path": "/page", + "Path": "/namespace/{namespaceID}/page", "APIs": [ { "Name": "list", diff --git a/api/compose/spec/record.json b/api/compose/spec/record.json index a45d56dc3..709f070a4 100644 --- a/api/compose/spec/record.json +++ b/api/compose/spec/record.json @@ -11,6 +11,12 @@ ], "Parameters": { "path": [ + { + "name": "namespaceID", + "required": true, + "title": "Namespace ID", + "type": "uint64" + }, { "name": "moduleID", "required": true, @@ -21,7 +27,7 @@ }, "Protocol": "", "Authentication": [], - "Path": "/module/{moduleID}/record", + "Path": "/namespace/{namespaceID}/module/{moduleID}/record", "APIs": [ { "Name": "report", diff --git a/api/compose/spec/trigger.json b/api/compose/spec/trigger.json index 0c6a6af2c..fafe5c3a4 100644 --- a/api/compose/spec/trigger.json +++ b/api/compose/spec/trigger.json @@ -3,10 +3,19 @@ "Description": "Compose Triggers", "Interface": "Trigger", "Struct": null, - "Parameters": null, + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "Namespace ID", + "type": "uint64" + } + ] + }, "Protocol": "", "Authentication": [], - "Path": "/trigger", + "Path": "/namespace/{namespaceID}/trigger", "APIs": [ { "Name": "list", diff --git a/compose/rest/attachment.go b/compose/rest/attachment.go index c150b2f07..b6417e1bd 100644 --- a/compose/rest/attachment.go +++ b/compose/rest/attachment.go @@ -42,10 +42,11 @@ func (Attachment) New() *Attachment { // Attachments returns list of all files attached to records func (ctrl *Attachment) List(ctx context.Context, r *request.AttachmentList) (interface{}, error) { f := types.AttachmentFilter{ - Kind: r.Kind, - ModuleID: r.ModuleID, - RecordID: r.RecordID, - FieldName: r.FieldName, + NamespaceID: r.NamespaceID, + Kind: r.Kind, + ModuleID: r.ModuleID, + RecordID: r.RecordID, + FieldName: r.FieldName, // Filter: r.Filter, PerPage: r.PerPage, Page: r.Page, diff --git a/compose/rest/handlers/attachment.go b/compose/rest/handlers/attachment.go index 502825af7..e2e641e1e 100644 --- a/compose/rest/handlers/attachment.go +++ b/compose/rest/handlers/attachment.go @@ -130,9 +130,9 @@ func NewAttachment(ah AttachmentAPI) *Attachment { func (ah *Attachment) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) - r.Get("/attachment/{kind}/", ah.List) - r.Get("/attachment/{kind}/{attachmentID}", ah.Details) - r.Get("/attachment/{kind}/{attachmentID}/original/{name}", ah.Original) - r.Get("/attachment/{kind}/{attachmentID}/preview.{ext}", ah.Preview) + r.Get("/namespace/{namespaceID}/attachment/{kind}/", ah.List) + r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}", ah.Details) + r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/original/{name}", ah.Original) + r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/preview.{ext}", ah.Preview) }) } diff --git a/compose/rest/handlers/chart.go b/compose/rest/handlers/chart.go index b758d2f19..2b5b01d3f 100644 --- a/compose/rest/handlers/chart.go +++ b/compose/rest/handlers/chart.go @@ -152,10 +152,10 @@ func NewChart(ch ChartAPI) *Chart { func (ch *Chart) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) - r.Get("/chart/", ch.List) - r.Post("/chart/", ch.Create) - r.Get("/chart/{chartID}", ch.Read) - r.Post("/chart/{chartID}", ch.Update) - r.Delete("/chart/{chartID}", ch.Delete) + r.Get("/namespace/{namespaceID}/chart/", ch.List) + r.Post("/namespace/{namespaceID}/chart/", ch.Create) + r.Get("/namespace/{namespaceID}/chart/{chartID}", ch.Read) + r.Post("/namespace/{namespaceID}/chart/{chartID}", ch.Update) + r.Delete("/namespace/{namespaceID}/chart/{chartID}", ch.Delete) }) } diff --git a/compose/rest/handlers/module.go b/compose/rest/handlers/module.go index 7006744b9..b42c7153d 100644 --- a/compose/rest/handlers/module.go +++ b/compose/rest/handlers/module.go @@ -152,10 +152,10 @@ func NewModule(mh ModuleAPI) *Module { func (mh *Module) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) - r.Get("/module/", mh.List) - r.Post("/module/", mh.Create) - r.Get("/module/{moduleID}", mh.Read) - r.Post("/module/{moduleID}", mh.Update) - r.Delete("/module/{moduleID}", mh.Delete) + r.Get("/namespace/{namespaceID}/module/", mh.List) + r.Post("/namespace/{namespaceID}/module/", mh.Create) + r.Get("/namespace/{namespaceID}/module/{moduleID}", mh.Read) + r.Post("/namespace/{namespaceID}/module/{moduleID}", mh.Update) + r.Delete("/namespace/{namespaceID}/module/{moduleID}", mh.Delete) }) } diff --git a/compose/rest/handlers/page.go b/compose/rest/handlers/page.go index a4b19af44..f8437b910 100644 --- a/compose/rest/handlers/page.go +++ b/compose/rest/handlers/page.go @@ -218,13 +218,13 @@ func NewPage(ph PageAPI) *Page { func (ph *Page) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) - r.Get("/page/", ph.List) - r.Post("/page/", ph.Create) - r.Get("/page/{pageID}", ph.Read) - r.Get("/page/tree", ph.Tree) - r.Post("/page/{pageID}", ph.Update) - r.Post("/page/{selfID}/reorder", ph.Reorder) - r.Delete("/page/{pageID}", ph.Delete) - r.Post("/page/{pageID}/attachment", ph.Upload) + r.Get("/namespace/{namespaceID}/page/", ph.List) + r.Post("/namespace/{namespaceID}/page/", ph.Create) + r.Get("/namespace/{namespaceID}/page/{pageID}", ph.Read) + r.Get("/namespace/{namespaceID}/page/tree", ph.Tree) + r.Post("/namespace/{namespaceID}/page/{pageID}", ph.Update) + r.Post("/namespace/{namespaceID}/page/{selfID}/reorder", ph.Reorder) + r.Delete("/namespace/{namespaceID}/page/{pageID}", ph.Delete) + r.Post("/namespace/{namespaceID}/page/{pageID}/attachment", ph.Upload) }) } diff --git a/compose/rest/handlers/record.go b/compose/rest/handlers/record.go index f2c652cb0..f63bbc621 100644 --- a/compose/rest/handlers/record.go +++ b/compose/rest/handlers/record.go @@ -196,12 +196,12 @@ func NewRecord(rh RecordAPI) *Record { func (rh *Record) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) - r.Get("/module/{moduleID}/record/report", rh.Report) - r.Get("/module/{moduleID}/record/", rh.List) - r.Post("/module/{moduleID}/record/", rh.Create) - r.Get("/module/{moduleID}/record/{recordID}", rh.Read) - r.Post("/module/{moduleID}/record/{recordID}", rh.Update) - r.Delete("/module/{moduleID}/record/{recordID}", rh.Delete) - r.Post("/module/{moduleID}/record/{recordID}/{fieldName}/attachment", rh.Upload) + r.Get("/namespace/{namespaceID}/module/{moduleID}/record/report", rh.Report) + r.Get("/namespace/{namespaceID}/module/{moduleID}/record/", rh.List) + r.Post("/namespace/{namespaceID}/module/{moduleID}/record/", rh.Create) + r.Get("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", rh.Read) + r.Post("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", rh.Update) + r.Delete("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", rh.Delete) + r.Post("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}/{fieldName}/attachment", rh.Upload) }) } diff --git a/compose/rest/handlers/trigger.go b/compose/rest/handlers/trigger.go index 6aaea99fa..277565b54 100644 --- a/compose/rest/handlers/trigger.go +++ b/compose/rest/handlers/trigger.go @@ -152,10 +152,10 @@ func NewTrigger(th TriggerAPI) *Trigger { func (th *Trigger) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) - r.Get("/trigger/", th.List) - r.Post("/trigger/", th.Create) - r.Get("/trigger/{triggerID}", th.Read) - r.Post("/trigger/{triggerID}", th.Update) - r.Delete("/trigger/{triggerID}", th.Delete) + r.Get("/namespace/{namespaceID}/trigger/", th.List) + r.Post("/namespace/{namespaceID}/trigger/", th.Create) + r.Get("/namespace/{namespaceID}/trigger/{triggerID}", th.Read) + r.Post("/namespace/{namespaceID}/trigger/{triggerID}", th.Update) + r.Delete("/namespace/{namespaceID}/trigger/{triggerID}", th.Delete) }) } diff --git a/compose/rest/request/attachment.go b/compose/rest/request/attachment.go index a10c63174..7325e683d 100644 --- a/compose/rest/request/attachment.go +++ b/compose/rest/request/attachment.go @@ -32,15 +32,16 @@ var _ = multipart.FileHeader{} // Attachment list request parameters type AttachmentList struct { - PageID uint64 `json:",string"` - ModuleID uint64 `json:",string"` - RecordID uint64 `json:",string"` - FieldName string - Page uint - PerPage uint - Sign string - UserID uint64 `json:",string"` - Kind string + PageID uint64 `json:",string"` + ModuleID uint64 `json:",string"` + RecordID uint64 `json:",string"` + FieldName string + Page uint + PerPage uint + Sign string + UserID uint64 `json:",string"` + Kind string + NamespaceID uint64 `json:",string"` } func NewAttachmentList() *AttachmentList { @@ -107,6 +108,7 @@ func (aReq *AttachmentList) Fill(r *http.Request) (err error) { aReq.UserID = parseUInt64(val) } aReq.Kind = chi.URLParam(r, "kind") + aReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -117,6 +119,7 @@ var _ RequestFiller = NewAttachmentList() type AttachmentDetails struct { AttachmentID uint64 `json:",string"` Kind string + NamespaceID uint64 `json:",string"` Sign string UserID uint64 `json:",string"` } @@ -154,6 +157,7 @@ func (aReq *AttachmentDetails) Fill(r *http.Request) (err error) { aReq.AttachmentID = parseUInt64(chi.URLParam(r, "attachmentID")) aReq.Kind = chi.URLParam(r, "kind") + aReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) if val, ok := get["sign"]; ok { aReq.Sign = val @@ -176,6 +180,7 @@ type AttachmentOriginal struct { AttachmentID uint64 `json:",string"` Name string Kind string + NamespaceID uint64 `json:",string"` } func NewAttachmentOriginal() *AttachmentOriginal { @@ -224,6 +229,7 @@ func (aReq *AttachmentOriginal) Fill(r *http.Request) (err error) { aReq.AttachmentID = parseUInt64(chi.URLParam(r, "attachmentID")) aReq.Name = chi.URLParam(r, "name") aReq.Kind = chi.URLParam(r, "kind") + aReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -235,6 +241,7 @@ type AttachmentPreview struct { AttachmentID uint64 `json:",string"` Ext string Kind string + NamespaceID uint64 `json:",string"` Sign string UserID uint64 `json:",string"` } @@ -273,6 +280,7 @@ func (aReq *AttachmentPreview) Fill(r *http.Request) (err error) { aReq.AttachmentID = parseUInt64(chi.URLParam(r, "attachmentID")) aReq.Ext = chi.URLParam(r, "ext") aReq.Kind = chi.URLParam(r, "kind") + aReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) if val, ok := get["sign"]; ok { aReq.Sign = val diff --git a/compose/rest/request/chart.go b/compose/rest/request/chart.go index 1437af312..b90d61713 100644 --- a/compose/rest/request/chart.go +++ b/compose/rest/request/chart.go @@ -34,6 +34,7 @@ var _ = multipart.FileHeader{} // Chart list request parameters type ChartList struct { + NamespaceID uint64 `json:",string"` } func NewChartList() *ChartList { @@ -67,6 +68,8 @@ func (cReq *ChartList) Fill(r *http.Request) (err error) { post[name] = string(param[0]) } + cReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) + return err } @@ -74,8 +77,9 @@ var _ RequestFiller = NewChartList() // Chart create request parameters type ChartCreate struct { - Config sqlxTypes.JSONText - Name string + Config sqlxTypes.JSONText + Name string + NamespaceID uint64 `json:",string"` } func NewChartCreate() *ChartCreate { @@ -119,6 +123,7 @@ func (cReq *ChartCreate) Fill(r *http.Request) (err error) { cReq.Name = val } + cReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -127,7 +132,8 @@ var _ RequestFiller = NewChartCreate() // Chart read request parameters type ChartRead struct { - ChartID uint64 `json:",string"` + ChartID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewChartRead() *ChartRead { @@ -162,6 +168,7 @@ func (cReq *ChartRead) Fill(r *http.Request) (err error) { } cReq.ChartID = parseUInt64(chi.URLParam(r, "chartID")) + cReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -170,9 +177,10 @@ var _ RequestFiller = NewChartRead() // Chart update request parameters type ChartUpdate struct { - ChartID uint64 `json:",string"` - Config sqlxTypes.JSONText - Name string + ChartID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` + Config sqlxTypes.JSONText + Name string } func NewChartUpdate() *ChartUpdate { @@ -207,6 +215,7 @@ func (cReq *ChartUpdate) Fill(r *http.Request) (err error) { } cReq.ChartID = parseUInt64(chi.URLParam(r, "chartID")) + cReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) if val, ok := post["config"]; ok { if cReq.Config, err = parseJSONTextWithErr(val); err != nil { @@ -225,7 +234,8 @@ var _ RequestFiller = NewChartUpdate() // Chart delete request parameters type ChartDelete struct { - ChartID uint64 `json:",string"` + ChartID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewChartDelete() *ChartDelete { @@ -260,6 +270,7 @@ func (cReq *ChartDelete) Fill(r *http.Request) (err error) { } cReq.ChartID = parseUInt64(chi.URLParam(r, "chartID")) + cReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } diff --git a/compose/rest/request/module.go b/compose/rest/request/module.go index e6cc06033..4d70c539d 100644 --- a/compose/rest/request/module.go +++ b/compose/rest/request/module.go @@ -35,7 +35,8 @@ var _ = multipart.FileHeader{} // Module list request parameters type ModuleList struct { - Query string + Query string + NamespaceID uint64 `json:",string"` } func NewModuleList() *ModuleList { @@ -73,6 +74,7 @@ func (mReq *ModuleList) Fill(r *http.Request) (err error) { mReq.Query = val } + mReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -81,9 +83,10 @@ var _ RequestFiller = NewModuleList() // Module create request parameters type ModuleCreate struct { - Name string - Fields types.ModuleFieldSet - Meta sqlxTypes.JSONText + Name string + Fields types.ModuleFieldSet + Meta sqlxTypes.JSONText + NamespaceID uint64 `json:",string"` } func NewModuleCreate() *ModuleCreate { @@ -127,6 +130,7 @@ func (mReq *ModuleCreate) Fill(r *http.Request) (err error) { return err } } + mReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -135,7 +139,8 @@ var _ RequestFiller = NewModuleCreate() // Module read request parameters type ModuleRead struct { - ModuleID uint64 `json:",string"` + ModuleID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewModuleRead() *ModuleRead { @@ -170,6 +175,7 @@ func (mReq *ModuleRead) Fill(r *http.Request) (err error) { } mReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) + mReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -178,10 +184,11 @@ var _ RequestFiller = NewModuleRead() // Module update request parameters type ModuleUpdate struct { - ModuleID uint64 `json:",string"` - Name string - Fields types.ModuleFieldSet - Meta sqlxTypes.JSONText + ModuleID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` + Name string + Fields types.ModuleFieldSet + Meta sqlxTypes.JSONText } func NewModuleUpdate() *ModuleUpdate { @@ -216,6 +223,7 @@ func (mReq *ModuleUpdate) Fill(r *http.Request) (err error) { } mReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) + mReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) if val, ok := post["name"]; ok { mReq.Name = val @@ -234,7 +242,8 @@ var _ RequestFiller = NewModuleUpdate() // Module delete request parameters type ModuleDelete struct { - ModuleID uint64 `json:",string"` + ModuleID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewModuleDelete() *ModuleDelete { @@ -269,6 +278,7 @@ func (mReq *ModuleDelete) Fill(r *http.Request) (err error) { } mReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) + mReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } diff --git a/compose/rest/request/page.go b/compose/rest/request/page.go index 7d0727fb0..15f0e3e95 100644 --- a/compose/rest/request/page.go +++ b/compose/rest/request/page.go @@ -34,7 +34,8 @@ var _ = multipart.FileHeader{} // Page list request parameters type PageList struct { - SelfID uint64 `json:",string"` + SelfID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewPageList() *PageList { @@ -72,6 +73,7 @@ func (pReq *PageList) Fill(r *http.Request) (err error) { pReq.SelfID = parseUInt64(val) } + pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -86,6 +88,7 @@ type PageCreate struct { Description string Visible bool Blocks sqlxTypes.JSONText + NamespaceID uint64 `json:",string"` } func NewPageCreate() *PageCreate { @@ -145,6 +148,7 @@ func (pReq *PageCreate) Fill(r *http.Request) (err error) { return err } } + pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -153,7 +157,8 @@ var _ RequestFiller = NewPageCreate() // Page read request parameters type PageRead struct { - PageID uint64 `json:",string"` + PageID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewPageRead() *PageRead { @@ -188,6 +193,7 @@ func (pReq *PageRead) Fill(r *http.Request) (err error) { } pReq.PageID = parseUInt64(chi.URLParam(r, "pageID")) + pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -196,6 +202,7 @@ var _ RequestFiller = NewPageRead() // Page tree request parameters type PageTree struct { + NamespaceID uint64 `json:",string"` } func NewPageTree() *PageTree { @@ -229,6 +236,8 @@ func (pReq *PageTree) Fill(r *http.Request) (err error) { post[name] = string(param[0]) } + pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) + return err } @@ -237,6 +246,7 @@ var _ RequestFiller = NewPageTree() // Page update request parameters type PageUpdate struct { PageID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` SelfID uint64 `json:",string"` ModuleID uint64 `json:",string"` Title string @@ -277,6 +287,7 @@ func (pReq *PageUpdate) Fill(r *http.Request) (err error) { } pReq.PageID = parseUInt64(chi.URLParam(r, "pageID")) + pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) if val, ok := post["selfID"]; ok { pReq.SelfID = parseUInt64(val) @@ -311,8 +322,9 @@ var _ RequestFiller = NewPageUpdate() // Page reorder request parameters type PageReorder struct { - SelfID uint64 `json:",string"` - PageIDs []string + SelfID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` + PageIDs []string } func NewPageReorder() *PageReorder { @@ -347,6 +359,7 @@ func (pReq *PageReorder) Fill(r *http.Request) (err error) { } pReq.SelfID = parseUInt64(chi.URLParam(r, "selfID")) + pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -355,7 +368,8 @@ var _ RequestFiller = NewPageReorder() // Page delete request parameters type PageDelete struct { - PageID uint64 `json:",string"` + PageID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewPageDelete() *PageDelete { @@ -390,6 +404,7 @@ func (pReq *PageDelete) Fill(r *http.Request) (err error) { } pReq.PageID = parseUInt64(chi.URLParam(r, "pageID")) + pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -398,8 +413,9 @@ var _ RequestFiller = NewPageDelete() // Page upload request parameters type PageUpload struct { - PageID uint64 `json:",string"` - Upload *multipart.FileHeader + PageID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` + Upload *multipart.FileHeader } func NewPageUpload() *PageUpload { @@ -434,6 +450,7 @@ func (pReq *PageUpload) Fill(r *http.Request) (err error) { } pReq.PageID = parseUInt64(chi.URLParam(r, "pageID")) + pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) if _, pReq.Upload, err = r.FormFile("upload"); err != nil { return errors.Wrap(err, "error procesing uploaded file") } diff --git a/compose/rest/request/record.go b/compose/rest/request/record.go index b15d50fe3..ac07bdb4a 100644 --- a/compose/rest/request/record.go +++ b/compose/rest/request/record.go @@ -34,10 +34,11 @@ var _ = multipart.FileHeader{} // Record report request parameters type RecordReport struct { - Metrics string - Dimensions string - Filter string - ModuleID uint64 `json:",string"` + Metrics string + Dimensions string + Filter string + NamespaceID uint64 `json:",string"` + ModuleID uint64 `json:",string"` } func NewRecordReport() *RecordReport { @@ -83,6 +84,7 @@ func (rReq *RecordReport) Fill(r *http.Request) (err error) { rReq.Filter = val } + rReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) return err @@ -92,11 +94,12 @@ var _ RequestFiller = NewRecordReport() // Record list request parameters type RecordList struct { - Filter string - Page int - PerPage int - Sort string - ModuleID uint64 `json:",string"` + Filter string + Page int + PerPage int + Sort string + NamespaceID uint64 `json:",string"` + ModuleID uint64 `json:",string"` } func NewRecordList() *RecordList { @@ -146,6 +149,7 @@ func (rReq *RecordList) Fill(r *http.Request) (err error) { rReq.Sort = val } + rReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) return err @@ -155,8 +159,9 @@ var _ RequestFiller = NewRecordList() // Record create request parameters type RecordCreate struct { - Values types.RecordValueSet - ModuleID uint64 `json:",string"` + Values types.RecordValueSet + NamespaceID uint64 `json:",string"` + ModuleID uint64 `json:",string"` } func NewRecordCreate() *RecordCreate { @@ -190,6 +195,7 @@ func (rReq *RecordCreate) Fill(r *http.Request) (err error) { post[name] = string(param[0]) } + rReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) return err @@ -199,8 +205,9 @@ var _ RequestFiller = NewRecordCreate() // Record read request parameters type RecordRead struct { - RecordID uint64 `json:",string"` - ModuleID uint64 `json:",string"` + RecordID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` + ModuleID uint64 `json:",string"` } func NewRecordRead() *RecordRead { @@ -235,6 +242,7 @@ func (rReq *RecordRead) Fill(r *http.Request) (err error) { } rReq.RecordID = parseUInt64(chi.URLParam(r, "recordID")) + rReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) return err @@ -244,9 +252,10 @@ var _ RequestFiller = NewRecordRead() // Record update request parameters type RecordUpdate struct { - RecordID uint64 `json:",string"` - ModuleID uint64 `json:",string"` - Values types.RecordValueSet + RecordID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` + ModuleID uint64 `json:",string"` + Values types.RecordValueSet } func NewRecordUpdate() *RecordUpdate { @@ -281,6 +290,7 @@ func (rReq *RecordUpdate) Fill(r *http.Request) (err error) { } rReq.RecordID = parseUInt64(chi.URLParam(r, "recordID")) + rReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) return err @@ -290,8 +300,9 @@ var _ RequestFiller = NewRecordUpdate() // Record delete request parameters type RecordDelete struct { - RecordID uint64 `json:",string"` - ModuleID uint64 `json:",string"` + RecordID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` + ModuleID uint64 `json:",string"` } func NewRecordDelete() *RecordDelete { @@ -326,6 +337,7 @@ func (rReq *RecordDelete) Fill(r *http.Request) (err error) { } rReq.RecordID = parseUInt64(chi.URLParam(r, "recordID")) + rReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) return err @@ -335,10 +347,11 @@ var _ RequestFiller = NewRecordDelete() // Record upload request parameters type RecordUpload struct { - RecordID uint64 `json:",string"` - FieldName string - ModuleID uint64 `json:",string"` - Upload *multipart.FileHeader + RecordID uint64 `json:",string"` + FieldName string + NamespaceID uint64 `json:",string"` + ModuleID uint64 `json:",string"` + Upload *multipart.FileHeader } func NewRecordUpload() *RecordUpload { @@ -374,6 +387,7 @@ func (rReq *RecordUpload) Fill(r *http.Request) (err error) { rReq.RecordID = parseUInt64(chi.URLParam(r, "recordID")) rReq.FieldName = chi.URLParam(r, "fieldName") + rReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID")) if _, rReq.Upload, err = r.FormFile("upload"); err != nil { return errors.Wrap(err, "error procesing uploaded file") diff --git a/compose/rest/request/trigger.go b/compose/rest/request/trigger.go index eec77f593..9042deb69 100644 --- a/compose/rest/request/trigger.go +++ b/compose/rest/request/trigger.go @@ -32,7 +32,8 @@ var _ = multipart.FileHeader{} // Trigger list request parameters type TriggerList struct { - ModuleID uint64 `json:",string"` + ModuleID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewTriggerList() *TriggerList { @@ -70,6 +71,7 @@ func (tReq *TriggerList) Fill(r *http.Request) (err error) { tReq.ModuleID = parseUInt64(val) } + tReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -78,11 +80,12 @@ var _ RequestFiller = NewTriggerList() // Trigger create request parameters type TriggerCreate struct { - ModuleID uint64 `json:",string"` - Name string - Actions []string - Enabled bool - Source string + ModuleID uint64 `json:",string"` + Name string + Actions []string + Enabled bool + Source string + NamespaceID uint64 `json:",string"` } func NewTriggerCreate() *TriggerCreate { @@ -132,6 +135,7 @@ func (tReq *TriggerCreate) Fill(r *http.Request) (err error) { tReq.Source = val } + tReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -140,7 +144,8 @@ var _ RequestFiller = NewTriggerCreate() // Trigger read request parameters type TriggerRead struct { - TriggerID uint64 `json:",string"` + TriggerID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewTriggerRead() *TriggerRead { @@ -175,6 +180,7 @@ func (tReq *TriggerRead) Fill(r *http.Request) (err error) { } tReq.TriggerID = parseUInt64(chi.URLParam(r, "triggerID")) + tReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } @@ -183,12 +189,13 @@ var _ RequestFiller = NewTriggerRead() // Trigger update request parameters type TriggerUpdate struct { - TriggerID uint64 `json:",string"` - ModuleID uint64 `json:",string"` - Name string - Actions []string - Enabled bool - Source string + TriggerID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` + ModuleID uint64 `json:",string"` + Name string + Actions []string + Enabled bool + Source string } func NewTriggerUpdate() *TriggerUpdate { @@ -223,6 +230,7 @@ func (tReq *TriggerUpdate) Fill(r *http.Request) (err error) { } tReq.TriggerID = parseUInt64(chi.URLParam(r, "triggerID")) + tReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) if val, ok := post["moduleID"]; ok { tReq.ModuleID = parseUInt64(val) @@ -247,7 +255,8 @@ var _ RequestFiller = NewTriggerUpdate() // Trigger delete request parameters type TriggerDelete struct { - TriggerID uint64 `json:",string"` + TriggerID uint64 `json:",string"` + NamespaceID uint64 `json:",string"` } func NewTriggerDelete() *TriggerDelete { @@ -282,6 +291,7 @@ func (tReq *TriggerDelete) Fill(r *http.Request) (err error) { } tReq.TriggerID = parseUInt64(chi.URLParam(r, "triggerID")) + tReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err } diff --git a/docs/compose/README.md b/docs/compose/README.md index c9551c3d1..cde1eb7c8 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -2,10 +2,10 @@ | Method | Endpoint | Purpose | | ------ | -------- | ------- | -| `GET` | `/attachment/{kind}/` | List, filter all page attachments | -| `GET` | `/attachment/{kind}/{attachmentID}` | Attachment details | -| `GET` | `/attachment/{kind}/{attachmentID}/original/{name}` | Serves attached file | -| `GET` | `/attachment/{kind}/{attachmentID}/preview.{ext}` | Serves preview of an attached file | +| `GET` | `/namespace/{namespaceID}/attachment/{kind}/` | List, filter all page attachments | +| `GET` | `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}` | Attachment details | +| `GET` | `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/original/{name}` | Serves attached file | +| `GET` | `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/preview.{ext}` | Serves preview of an attached file | ## List, filter all page attachments @@ -13,7 +13,7 @@ | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/attachment/{kind}/` | HTTP/S | GET | Client ID, Session ID | +| `/namespace/{namespaceID}/attachment/{kind}/` | HTTP/S | GET | Client ID, Session ID | #### Request parameters @@ -28,6 +28,7 @@ | sign | string | GET | Signature | N/A | YES | | userID | uint64 | GET | User ID | N/A | YES | | kind | string | PATH | Attachment kind | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Attachment details @@ -35,7 +36,7 @@ | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/attachment/{kind}/{attachmentID}` | HTTP/S | GET | Client ID, Session ID | +| `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}` | HTTP/S | GET | Client ID, Session ID | #### Request parameters @@ -43,6 +44,7 @@ | --------- | ---- | ------ | ----------- | ------- | --------- | | attachmentID | uint64 | PATH | Attachment ID | N/A | YES | | kind | string | PATH | Attachment kind | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | sign | string | GET | Signature | N/A | YES | | userID | uint64 | GET | User ID | N/A | YES | @@ -52,7 +54,7 @@ | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/attachment/{kind}/{attachmentID}/original/{name}` | HTTP/S | GET | Client ID, Session ID | +| `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/original/{name}` | HTTP/S | GET | Client ID, Session ID | #### Request parameters @@ -64,6 +66,7 @@ | attachmentID | uint64 | PATH | Attachment ID | N/A | YES | | name | string | PATH | File name | N/A | YES | | kind | string | PATH | Attachment kind | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Serves preview of an attached file @@ -71,7 +74,7 @@ | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/attachment/{kind}/{attachmentID}/preview.{ext}` | HTTP/S | GET | Client ID, Session ID | +| `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/preview.{ext}` | HTTP/S | GET | Client ID, Session ID | #### Request parameters @@ -80,6 +83,7 @@ | attachmentID | uint64 | PATH | Attachment ID | N/A | YES | | ext | string | PATH | Preview extension/format | N/A | YES | | kind | string | PATH | Attachment kind | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | sign | string | GET | Signature | N/A | YES | | userID | uint64 | GET | User ID | N/A | YES | @@ -92,11 +96,11 @@ | Method | Endpoint | Purpose | | ------ | -------- | ------- | -| `GET` | `/chart/` | List/read charts from module section | -| `POST` | `/chart/` | List/read charts from module section | -| `GET` | `/chart/{chartID}` | Read charts by ID from module section | -| `POST` | `/chart/{chartID}` | Add/update charts in module section | -| `DELETE` | `/chart/{chartID}` | Delete chart | +| `GET` | `/namespace/{namespaceID}/chart/` | List/read charts from module section | +| `POST` | `/namespace/{namespaceID}/chart/` | List/read charts from module section | +| `GET` | `/namespace/{namespaceID}/chart/{chartID}` | Read charts by ID from module section | +| `POST` | `/namespace/{namespaceID}/chart/{chartID}` | Add/update charts in module section | +| `DELETE` | `/namespace/{namespaceID}/chart/{chartID}` | Delete chart | ## List/read charts from module section @@ -104,12 +108,13 @@ | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/chart/` | HTTP/S | GET | | +| `/namespace/{namespaceID}/chart/` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## List/read charts from module section @@ -117,7 +122,7 @@ | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/chart/` | HTTP/S | POST | | +| `/namespace/{namespaceID}/chart/` | HTTP/S | POST | | #### Request parameters @@ -125,6 +130,7 @@ | --------- | ---- | ------ | ----------- | ------- | --------- | | config | sqlxTypes.JSONText | POST | Chart JSON | N/A | YES | | name | string | POST | Chart name | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Read charts by ID from module section @@ -132,13 +138,14 @@ | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/chart/{chartID}` | HTTP/S | GET | | +| `/namespace/{namespaceID}/chart/{chartID}` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | chartID | uint64 | PATH | Chart ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Add/update charts in module section @@ -146,13 +153,14 @@ | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/chart/{chartID}` | HTTP/S | POST | | +| `/namespace/{namespaceID}/chart/{chartID}` | HTTP/S | POST | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | chartID | uint64 | PATH | Chart ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | config | sqlxTypes.JSONText | POST | Chart JSON | N/A | YES | | name | string | POST | Chart name | N/A | YES | @@ -162,13 +170,14 @@ | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/chart/{chartID}` | HTTP/S | DELETE | | +| `/namespace/{namespaceID}/chart/{chartID}` | HTTP/S | DELETE | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | chartID | uint64 | PATH | Chart ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | --- @@ -181,11 +190,11 @@ Compose module definitions | Method | Endpoint | Purpose | | ------ | -------- | ------- | -| `GET` | `/module/` | List modules | -| `POST` | `/module/` | Create module | -| `GET` | `/module/{moduleID}` | Read module | -| `POST` | `/module/{moduleID}` | Update module | -| `DELETE` | `/module/{moduleID}` | Delete module | +| `GET` | `/namespace/{namespaceID}/module/` | List modules | +| `POST` | `/namespace/{namespaceID}/module/` | Create module | +| `GET` | `/namespace/{namespaceID}/module/{moduleID}` | Read module | +| `POST` | `/namespace/{namespaceID}/module/{moduleID}` | Update module | +| `DELETE` | `/namespace/{namespaceID}/module/{moduleID}` | Delete module | ## List modules @@ -193,13 +202,14 @@ Compose module definitions | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/` | HTTP/S | GET | | +| `/namespace/{namespaceID}/module/` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | query | string | GET | Search query | N/A | NO | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Create module @@ -207,7 +217,7 @@ Compose module definitions | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/` | HTTP/S | POST | | +| `/namespace/{namespaceID}/module/` | HTTP/S | POST | | #### Request parameters @@ -216,6 +226,7 @@ Compose module definitions | name | string | POST | Module Name | N/A | YES | | fields | types.ModuleFieldSet | POST | Fields JSON | N/A | YES | | meta | sqlxTypes.JSONText | POST | Module meta data | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Read module @@ -223,13 +234,14 @@ Compose module definitions | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}` | HTTP/S | GET | | +| `/namespace/{namespaceID}/module/{moduleID}` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | moduleID | uint64 | PATH | Module ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Update module @@ -237,13 +249,14 @@ Compose module definitions | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}` | HTTP/S | POST | | +| `/namespace/{namespaceID}/module/{moduleID}` | HTTP/S | POST | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | moduleID | uint64 | PATH | Module ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | name | string | POST | Module Name | N/A | YES | | fields | types.ModuleFieldSet | POST | Fields JSON | N/A | YES | | meta | sqlxTypes.JSONText | POST | Module meta data | N/A | YES | @@ -254,13 +267,14 @@ Compose module definitions | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}` | HTTP/S | DELETE | | +| `/namespace/{namespaceID}/module/{moduleID}` | HTTP/S | DELETE | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | moduleID | uint64 | PATH | Module ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | --- @@ -399,14 +413,14 @@ Compose module pages | Method | Endpoint | Purpose | | ------ | -------- | ------- | -| `GET` | `/page/` | List available pages | -| `POST` | `/page/` | Create page | -| `GET` | `/page/{pageID}` | Get page details | -| `GET` | `/page/tree` | Get page all (non-record) pages, hierarchically | -| `POST` | `/page/{pageID}` | Update page | -| `POST` | `/page/{selfID}/reorder` | Reorder pages | -| `Delete` | `/page/{pageID}` | Delete page | -| `POST` | `/page/{pageID}/attachment` | Uploads attachment to page | +| `GET` | `/namespace/{namespaceID}/page/` | List available pages | +| `POST` | `/namespace/{namespaceID}/page/` | Create page | +| `GET` | `/namespace/{namespaceID}/page/{pageID}` | Get page details | +| `GET` | `/namespace/{namespaceID}/page/tree` | Get page all (non-record) pages, hierarchically | +| `POST` | `/namespace/{namespaceID}/page/{pageID}` | Update page | +| `POST` | `/namespace/{namespaceID}/page/{selfID}/reorder` | Reorder pages | +| `Delete` | `/namespace/{namespaceID}/page/{pageID}` | Delete page | +| `POST` | `/namespace/{namespaceID}/page/{pageID}/attachment` | Uploads attachment to page | ## List available pages @@ -414,13 +428,14 @@ Compose module pages | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/page/` | HTTP/S | GET | | +| `/namespace/{namespaceID}/page/` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | selfID | uint64 | GET | Parent page ID | N/A | NO | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Create page @@ -428,7 +443,7 @@ Compose module pages | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/page/` | HTTP/S | POST | | +| `/namespace/{namespaceID}/page/` | HTTP/S | POST | | #### Request parameters @@ -440,6 +455,7 @@ Compose module pages | description | string | POST | Description | N/A | NO | | visible | bool | POST | Visible in navigation | N/A | NO | | blocks | sqlxTypes.JSONText | POST | Blocks JSON | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Get page details @@ -447,13 +463,14 @@ Compose module pages | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/page/{pageID}` | HTTP/S | GET | | +| `/namespace/{namespaceID}/page/{pageID}` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | pageID | uint64 | PATH | Page ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Get page all (non-record) pages, hierarchically @@ -461,12 +478,13 @@ Compose module pages | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/page/tree` | HTTP/S | GET | | +| `/namespace/{namespaceID}/page/tree` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Update page @@ -474,13 +492,14 @@ Compose module pages | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/page/{pageID}` | HTTP/S | POST | | +| `/namespace/{namespaceID}/page/{pageID}` | HTTP/S | POST | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | pageID | uint64 | PATH | Page ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | selfID | uint64 | POST | Parent Page ID | N/A | NO | | moduleID | uint64 | POST | Module ID (optional) | N/A | NO | | title | string | POST | Title | N/A | YES | @@ -494,13 +513,14 @@ Compose module pages | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/page/{selfID}/reorder` | HTTP/S | POST | | +| `/namespace/{namespaceID}/page/{selfID}/reorder` | HTTP/S | POST | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | selfID | uint64 | PATH | Parent page ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | pageIDs | []string | POST | Page ID order | N/A | YES | ## Delete page @@ -509,13 +529,14 @@ Compose module pages | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/page/{pageID}` | HTTP/S | Delete | | +| `/namespace/{namespaceID}/page/{pageID}` | HTTP/S | Delete | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | pageID | uint64 | PATH | Page ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Uploads attachment to page @@ -523,13 +544,14 @@ Compose module pages | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/page/{pageID}/attachment` | HTTP/S | POST | | +| `/namespace/{namespaceID}/page/{pageID}/attachment` | HTTP/S | POST | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | pageID | uint64 | PATH | Page ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | upload | *multipart.FileHeader | POST | File to upload | N/A | YES | --- @@ -568,13 +590,13 @@ Compose records | Method | Endpoint | Purpose | | ------ | -------- | ------- | -| `GET` | `/module/{moduleID}/record/report` | Generates report from module records | -| `GET` | `/module/{moduleID}/record/` | List/read records from module section | -| `POST` | `/module/{moduleID}/record/` | Create record in module section | -| `GET` | `/module/{moduleID}/record/{recordID}` | Read records by ID from module section | -| `POST` | `/module/{moduleID}/record/{recordID}` | Update records in module section | -| `DELETE` | `/module/{moduleID}/record/{recordID}` | Delete record row from module section | -| `POST` | `/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | Uploads attachment and validates it against record field requirements | +| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/report` | Generates report from module records | +| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/` | List/read records from module section | +| `POST` | `/namespace/{namespaceID}/module/{moduleID}/record/` | Create record in module section | +| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | Read records by ID from module section | +| `POST` | `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | Update records in module section | +| `DELETE` | `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | Delete record row from module section | +| `POST` | `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | Uploads attachment and validates it against record field requirements | ## Generates report from module records @@ -582,7 +604,7 @@ Compose records | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/report` | HTTP/S | GET | | +| `/namespace/{namespaceID}/module/{moduleID}/record/report` | HTTP/S | GET | | #### Request parameters @@ -591,6 +613,7 @@ Compose records | metrics | string | GET | Metrics (eg: 'SUM(money), MAX(calls)') | N/A | NO | | dimensions | string | GET | Dimensions (eg: 'DATE(foo), status') | N/A | YES | | filter | string | GET | Filter (eg: 'DATE(foo) > 2010') | N/A | NO | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | moduleID | uint64 | PATH | Module ID | N/A | YES | ## List/read records from module section @@ -599,7 +622,7 @@ Compose records | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/` | HTTP/S | GET | | +| `/namespace/{namespaceID}/module/{moduleID}/record/` | HTTP/S | GET | | #### Request parameters @@ -609,6 +632,7 @@ Compose records | page | int | GET | Page number (0 based) | N/A | NO | | perPage | int | GET | Returned items per page (default 50) | N/A | NO | | sort | string | GET | Sort field (default id desc) | N/A | NO | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | moduleID | uint64 | PATH | Module ID | N/A | YES | ## Create record in module section @@ -617,13 +641,14 @@ Compose records | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/` | HTTP/S | POST | | +| `/namespace/{namespaceID}/module/{moduleID}/record/` | HTTP/S | POST | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | values | types.RecordValueSet | POST | Record values | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | moduleID | uint64 | PATH | Module ID | N/A | YES | ## Read records by ID from module section @@ -632,13 +657,14 @@ Compose records | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/{recordID}` | HTTP/S | GET | | +| `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | recordID | uint64 | PATH | Record ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | moduleID | uint64 | PATH | Module ID | N/A | YES | ## Update records in module section @@ -647,13 +673,14 @@ Compose records | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/{recordID}` | HTTP/S | POST | | +| `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | HTTP/S | POST | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | recordID | uint64 | PATH | Record ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | moduleID | uint64 | PATH | Module ID | N/A | YES | | values | types.RecordValueSet | POST | Record values | N/A | YES | @@ -663,13 +690,14 @@ Compose records | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/{recordID}` | HTTP/S | DELETE | | +| `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | HTTP/S | DELETE | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | recordID | uint64 | PATH | Record ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | moduleID | uint64 | PATH | Module ID | N/A | YES | ## Uploads attachment and validates it against record field requirements @@ -678,7 +706,7 @@ Compose records | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | HTTP/S | POST | | +| `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | HTTP/S | POST | | #### Request parameters @@ -686,6 +714,7 @@ Compose records | --------- | ---- | ------ | ----------- | ------- | --------- | | recordID | uint64 | PATH | Record ID | N/A | YES | | fieldName | string | PATH | Field name | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | moduleID | uint64 | PATH | Module ID | N/A | YES | | upload | *multipart.FileHeader | POST | File to upload | N/A | YES | @@ -700,11 +729,11 @@ Compose Triggers | Method | Endpoint | Purpose | | ------ | -------- | ------- | -| `GET` | `/trigger/` | List available triggers | -| `POST` | `/trigger/` | Create trigger | -| `GET` | `/trigger/{triggerID}` | Get trigger details | -| `POST` | `/trigger/{triggerID}` | Update trigger | -| `Delete` | `/trigger/{triggerID}` | Delete trigger | +| `GET` | `/namespace/{namespaceID}/trigger/` | List available triggers | +| `POST` | `/namespace/{namespaceID}/trigger/` | Create trigger | +| `GET` | `/namespace/{namespaceID}/trigger/{triggerID}` | Get trigger details | +| `POST` | `/namespace/{namespaceID}/trigger/{triggerID}` | Update trigger | +| `Delete` | `/namespace/{namespaceID}/trigger/{triggerID}` | Delete trigger | ## List available triggers @@ -712,13 +741,14 @@ Compose Triggers | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/trigger/` | HTTP/S | GET | | +| `/namespace/{namespaceID}/trigger/` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | moduleID | uint64 | GET | Filter triggers by module | N/A | NO | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Create trigger @@ -726,7 +756,7 @@ Compose Triggers | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/trigger/` | HTTP/S | POST | | +| `/namespace/{namespaceID}/trigger/` | HTTP/S | POST | | #### Request parameters @@ -737,6 +767,7 @@ Compose Triggers | actions | []string | POST | Actions that trigger this trigger | N/A | NO | | enabled | bool | POST | Enabled | N/A | NO | | source | string | POST | Trigger source code | N/A | NO | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Get trigger details @@ -744,13 +775,14 @@ Compose Triggers | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/trigger/{triggerID}` | HTTP/S | GET | | +| `/namespace/{namespaceID}/trigger/{triggerID}` | HTTP/S | GET | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | triggerID | uint64 | PATH | Trigger ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Update trigger @@ -758,13 +790,14 @@ Compose Triggers | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/trigger/{triggerID}` | HTTP/S | POST | | +| `/namespace/{namespaceID}/trigger/{triggerID}` | HTTP/S | POST | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | triggerID | uint64 | PATH | Trigger ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | moduleID | uint64 | POST | Module ID | N/A | NO | | name | string | POST | Name | N/A | YES | | actions | []string | POST | Actions that trigger this trigger | N/A | NO | @@ -777,12 +810,13 @@ Compose Triggers | URI | Protocol | Method | Authentication | | --- | -------- | ------ | -------------- | -| `/trigger/{triggerID}` | HTTP/S | Delete | | +| `/namespace/{namespaceID}/trigger/{triggerID}` | HTTP/S | Delete | | #### Request parameters | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | triggerID | uint64 | PATH | Trigger ID | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | --- \ No newline at end of file From 8ef7b77a770f1eb111124ab7a84e218d642236a0 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sun, 28 Apr 2019 12:35:08 +0200 Subject: [PATCH 04/14] Namespace changes on all types, attachment repository, service --- api/compose/spec.json | 20 ++- api/compose/spec/attachment.json | 20 ++- compose/db/mysql/static.go | 2 +- .../20190427180922.change-tbl-prefix.up.sql | 4 +- .../20190428080000.namespace-refs.up.sql | 73 ++++++++++ compose/internal/repository/attachment.go | 137 ++++++++---------- compose/internal/repository/namespace.go | 134 ++++++----------- compose/internal/repository/repository.go | 77 ++++++++++ compose/internal/service/attachment.go | 71 ++++++--- compose/internal/service/error.go | 1 + compose/internal/service/namespace.go | 45 +++--- compose/internal/service/service.go | 16 ++ compose/rest/attachment.go | 110 +++++++------- compose/rest/handlers/attachment.go | 35 ++++- compose/rest/namespace.go | 13 +- compose/rest/page.go | 8 +- compose/rest/record.go | 12 +- compose/rest/request/attachment.go | 69 ++++++++- compose/types/attachment.go | 44 +++--- compose/types/chart.go | 10 ++ compose/types/module.go | 2 + compose/types/namespace.go | 18 +-- compose/types/page.go | 2 + compose/types/record.go | 2 + compose/types/trigger.go | 2 + docs/compose/README.md | 21 ++- 26 files changed, 611 insertions(+), 337 deletions(-) create mode 100644 compose/db/schema/mysql/20190428080000.namespace-refs.up.sql diff --git a/api/compose/spec.json b/api/compose/spec.json index 35839d4b9..5a3c7f89d 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -1129,7 +1129,7 @@ "type": "uint64", "name": "moduleID", "required": false, - "title": "Filter attachments by mnodule ID" + "title": "Filter attachments by module ID" }, { "name": "recordID", @@ -1159,7 +1159,7 @@ } }, { - "name": "details", + "name": "read", "path": "/{attachmentID}", "method": "GET", "title": "Attachment details", @@ -1174,6 +1174,22 @@ ] } }, + { + "name": "delete", + "path": "/{attachmentID}", + "method": "DELETE", + "title": "Delete attachment", + "parameters": { + "path": [ + { + "name": "attachmentID", + "type": "uint64", + "required": true, + "title": "Attachment ID" + } + ] + } + }, { "name": "original", "path": "/{attachmentID}/original/{name}", diff --git a/api/compose/spec/attachment.json b/api/compose/spec/attachment.json index 80cf0d9f0..6f4873af2 100644 --- a/api/compose/spec/attachment.json +++ b/api/compose/spec/attachment.json @@ -55,7 +55,7 @@ { "name": "moduleID", "required": false, - "title": "Filter attachments by mnodule ID", + "title": "Filter attachments by module ID", "type": "uint64" }, { @@ -86,7 +86,7 @@ } }, { - "Name": "details", + "Name": "read", "Method": "GET", "Title": "Attachment details", "Path": "/{attachmentID}", @@ -101,6 +101,22 @@ ] } }, + { + "Name": "delete", + "Method": "DELETE", + "Title": "Delete attachment", + "Path": "/{attachmentID}", + "Parameters": { + "path": [ + { + "name": "attachmentID", + "required": true, + "title": "Attachment ID", + "type": "uint64" + } + ] + } + }, { "Name": "original", "Method": "GET", diff --git a/compose/db/mysql/static.go b/compose/db/mysql/static.go index 988dd5f27..f0543a7b4 100644 --- a/compose/db/mysql/static.go +++ b/compose/db/mysql/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package mysql -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_content\n RENAME TO compose_content;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x8a)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x816,\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\xf3-\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x15\x00\x15\x00\xcc\x07\x00\x00_.\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO compose_namespace (id, name, slug, enabled, meta) VALUES (88714882739863655, 'Crust CRM', 'crm', true, '{}');\n\nALTER TABLE `compose_attachment`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_chart`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_module`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_page`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_trigger`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record_value`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `record_id`,\n ADD INDEX (`rel_namespace`);\n\nUPDATE `compose_attachment` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_chart` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_module` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_page` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_trigger` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record_value` SET `rel_namespace` = 88714882739863655;\n\n\nALTER TABLE `compose_attachment`\n ADD CONSTRAINT `compose_attachment_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_chart`\n ADD CONSTRAINT `compose_chart_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_module`\n ADD CONSTRAINT `compose_module_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_page`\n ADD CONSTRAINT `compose_page_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record`\n ADD CONSTRAINT `compose_record_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_trigger`\n ADD CONSTRAINT `compose_trigger_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record_value`\n ADD CONSTRAINT `compose_record_value_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\nPK\x07\x08\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81v)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\",\x00\x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81`7\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\x1d9\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x16\x00\x16\x00'\x08\x00\x00\x899\x00\x00\x00\x00" diff --git a/compose/db/schema/mysql/20190427180922.change-tbl-prefix.up.sql b/compose/db/schema/mysql/20190427180922.change-tbl-prefix.up.sql index dfb25b246..f8cb60e27 100644 --- a/compose/db/schema/mysql/20190427180922.change-tbl-prefix.up.sql +++ b/compose/db/schema/mysql/20190427180922.change-tbl-prefix.up.sql @@ -1,5 +1,6 @@ DROP TABLE IF EXISTS crm_field; DROP TABLE IF EXISTS crm_fields; +DROP TABLE IF EXISTS crm_content; DROP TABLE IF EXISTS crm_content_links; DROP TABLE IF EXISTS crm_content_column; DROP TABLE IF EXISTS crm_module_content; @@ -10,9 +11,6 @@ ALTER TABLE crm_attachment ALTER TABLE crm_chart RENAME TO compose_chart; -ALTER TABLE crm_content - RENAME TO compose_content; - ALTER TABLE crm_module RENAME TO compose_module; diff --git a/compose/db/schema/mysql/20190428080000.namespace-refs.up.sql b/compose/db/schema/mysql/20190428080000.namespace-refs.up.sql new file mode 100644 index 000000000..26a9287bd --- /dev/null +++ b/compose/db/schema/mysql/20190428080000.namespace-refs.up.sql @@ -0,0 +1,73 @@ +INSERT INTO compose_namespace (id, name, slug, enabled, meta) VALUES (88714882739863655, 'Crust CRM', 'crm', true, '{}'); + +ALTER TABLE `compose_attachment` + ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`, + ADD INDEX (`rel_namespace`); + +ALTER TABLE `compose_chart` + ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`, + ADD INDEX (`rel_namespace`); + +ALTER TABLE `compose_module` + ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`, + ADD INDEX (`rel_namespace`); + +ALTER TABLE `compose_page` + ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`, + ADD INDEX (`rel_namespace`); + +ALTER TABLE `compose_record` + ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`, + ADD INDEX (`rel_namespace`); + +ALTER TABLE `compose_trigger` + ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`, + ADD INDEX (`rel_namespace`); + +ALTER TABLE `compose_record_value` + ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `record_id`, + ADD INDEX (`rel_namespace`); + +UPDATE `compose_attachment` SET `rel_namespace` = 88714882739863655; +UPDATE `compose_chart` SET `rel_namespace` = 88714882739863655; +UPDATE `compose_module` SET `rel_namespace` = 88714882739863655; +UPDATE `compose_page` SET `rel_namespace` = 88714882739863655; +UPDATE `compose_record` SET `rel_namespace` = 88714882739863655; +UPDATE `compose_trigger` SET `rel_namespace` = 88714882739863655; +UPDATE `compose_record_value` SET `rel_namespace` = 88714882739863655; + + +ALTER TABLE `compose_attachment` + ADD CONSTRAINT `compose_attachment_namespace` + FOREIGN KEY (`rel_namespace`) + REFERENCES `compose_namespace` (`id`); + +ALTER TABLE `compose_chart` + ADD CONSTRAINT `compose_chart_namespace` + FOREIGN KEY (`rel_namespace`) + REFERENCES `compose_namespace` (`id`); + +ALTER TABLE `compose_module` + ADD CONSTRAINT `compose_module_namespace` + FOREIGN KEY (`rel_namespace`) + REFERENCES `compose_namespace` (`id`); + +ALTER TABLE `compose_page` + ADD CONSTRAINT `compose_page_namespace` + FOREIGN KEY (`rel_namespace`) + REFERENCES `compose_namespace` (`id`); + +ALTER TABLE `compose_record` + ADD CONSTRAINT `compose_record_namespace` + FOREIGN KEY (`rel_namespace`) + REFERENCES `compose_namespace` (`id`); + +ALTER TABLE `compose_trigger` + ADD CONSTRAINT `compose_trigger_namespace` + FOREIGN KEY (`rel_namespace`) + REFERENCES `compose_namespace` (`id`); + +ALTER TABLE `compose_record_value` + ADD CONSTRAINT `compose_record_value_namespace` + FOREIGN KEY (`rel_namespace`) + REFERENCES `compose_namespace` (`id`); diff --git a/compose/internal/repository/attachment.go b/compose/internal/repository/attachment.go index 373b20c81..c12182829 100644 --- a/compose/internal/repository/attachment.go +++ b/compose/internal/repository/attachment.go @@ -4,10 +4,9 @@ import ( "context" "time" - "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/titpetric/factory" - sq "gopkg.in/Masterminds/squirrel.v1" + "gopkg.in/Masterminds/squirrel.v1" "github.com/crusttech/crust/compose/types" ) @@ -17,10 +16,9 @@ type ( With(ctx context.Context, db *factory.DB) AttachmentRepository Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) - FindByID(id uint64) (*types.Attachment, error) - FindByIDs(IDs ...uint64) (types.AttachmentSet, error) + FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error) Create(mod *types.Attachment) (*types.Attachment, error) - DeleteByID(id uint64) error + DeleteByID(namespaceID, attachmentID uint64) error } attachment struct { @@ -29,64 +27,68 @@ type ( ) const ( - sqlAttachmentColumns = ` - a.id, a.rel_owner, a.kind, - a.url, a.preview_url, - a.name, - a.meta, - a.created_at, a.updated_at, a.deleted_at - ` - sqlAttachmentScope = "deleted_at IS NULL" - - sqlAttachmentByID = `SELECT ` + sqlAttachmentColumns + - ` FROM compose_attachment AS a WHERE id = ? AND ` + sqlAttachmentScope - - sqlAttachmentsByIDs = `SELECT ` + sqlAttachmentColumns + - ` FROM compose_attachment AS a WHERE id IN (?) AND ` + sqlAttachmentScope + ErrAttachmentNotFound = repositoryError("AttachmentNotFound") ) func Attachment(ctx context.Context, db *factory.DB) AttachmentRepository { return (&attachment{}).With(ctx, db) } -func (r *attachment) With(ctx context.Context, db *factory.DB) AttachmentRepository { +func (r attachment) With(ctx context.Context, db *factory.DB) AttachmentRepository { return &attachment{ repository: r.repository.With(ctx, db), } } -func (r *attachment) FindByID(id uint64) (*types.Attachment, error) { - mod := &types.Attachment{} - - return mod, r.db().Get(mod, sqlAttachmentByID, id) +func (r attachment) table() string { + return "compose_attachment" } -func (r *attachment) FindByIDs(IDs ...uint64) (rval types.AttachmentSet, err error) { - rval = make([]*types.Attachment, 0) - - if len(IDs) == 0 { - return - } - - if sql, args, err := sqlx.In(sqlAttachmentsByIDs, IDs); err != nil { - return nil, err - } else { - return rval, r.db().Select(&rval, sql, args...) +func (r attachment) columns() []string { + return []string{ + "a.id", "a.rel_namespace", "a.rel_owner", "a.kind", + "a.url", "a.preview_url", + "a.name", + "a.meta", + "a.created_at", "a.updated_at", "a.deleted_at", } } -func (r *attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) { +func (r attachment) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table() + " AS a"). + Where("a.deleted_at IS NULL") + +} + +func (r attachment) FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("a.id = ?", attachmentID) + + a = &types.Attachment{} + ) + + if namespaceID > 0 { + query = query.Where("a.rel_namespace = ?", namespaceID) + } + + return a, isFound(r.fetchOne(a, query), a.ID > 0, ErrAttachmentNotFound) +} + +func (r attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) { f = filter - if f.PerPage > 100 { - f.PerPage = 100 - } else if f.PerPage == 0 { - f.PerPage = 50 + f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50) + + query := r.query(). + Where(squirrel.Eq{"a.kind": f.Kind}) + + if filter.NamespaceID > 0 { + query = query.Where("a.rel_namespace = ?", filter.NamespaceID) } - set = types.AttachmentSet{} - - query := sq.Select().From("compose_attachment AS a").Where(sq.Eq{"a.kind": f.Kind}) - switch f.Kind { case types.PageAttachment: // @todo implement filtering by page @@ -101,15 +103,15 @@ func (r *attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSe if f.ModuleID > 0 { query = query. Join("compose_record AS r ON (r.id = v.record_id)"). - Where(sq.Eq{"r.module_id": f.ModuleID}) + Where(squirrel.Eq{"r.module_id": f.ModuleID}) } if f.RecordID > 0 { - query = query.Where(sq.Eq{"v.record_id": f.RecordID}) + query = query.Where(squirrel.Eq{"v.record_id": f.RecordID}) } if f.FieldName != "" { - query = query.Where(sq.Eq{"v.name": f.FieldName}) + query = query.Where(squirrel.Eq{"v.name": f.FieldName}) } default: err = errors.New("unsupported kind value") @@ -120,46 +122,33 @@ func (r *attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSe return } - // Assemble SQL for counting (includes only where) - count := query.Column("COUNT(*)") - if sqlSelect, argsSelect, err := count.ToSql(); err != nil { - return set, f, err - } else { - // Execute count query. - if err := r.db().Get(&f.Count, sqlSelect, argsSelect...); err != nil { - return set, f, err - } - - // Return empty response if count of records is zero. - if f.Count == 0 { - return set, f, nil - } + if f.Count, err = r.count(query); err != nil || f.Count == 0 { + return } - // Assemble SQL for fetching attachments (where + sorting + paging)... query = query. - Column(sqlAttachmentColumns). - Limit(uint64(f.PerPage)). - Offset(uint64(f.Page * f.PerPage)) + Columns(r.columns()...). + OrderBy("id ASC") - if sqlSelect, argsSelect, err := query.ToSql(); err != nil { - return set, f, err - } else { - return set, f, r.db().Select(&set, sqlSelect, argsSelect...) - } + return set, f, r.fetchPaged(&set, query, f.Page, f.PerPage) } -func (r *attachment) Create(mod *types.Attachment) (*types.Attachment, error) { +func (r attachment) Create(mod *types.Attachment) (*types.Attachment, error) { if mod.ID == 0 { mod.ID = factory.Sonyflake.NextID() } mod.CreatedAt = time.Now() - return mod, r.db().Insert("compose_attachment", mod) + return mod, r.db().Insert(r.table(), mod) } -func (r *attachment) DeleteByID(id uint64) error { - _, err := r.db().Exec("UPDATE compose_attachment SET deleted_at = NOW() WHERE id = ?", id) +func (r attachment) DeleteByID(namespaceID, attachmentID uint64) error { + _, err := r.db().Exec( + "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", + namespaceID, + attachmentID, + ) + return err } diff --git a/compose/internal/repository/namespace.go b/compose/internal/repository/namespace.go index b6a2af4ce..11c5d870c 100644 --- a/compose/internal/repository/namespace.go +++ b/compose/internal/repository/namespace.go @@ -51,95 +51,6 @@ func (r namespace) columns() []string { } } -func (r *namespace) With(ctx context.Context, db *factory.DB) NamespaceRepository { - return &namespace{ - repository: r.repository.With(ctx, db), - } -} - -func (r *namespace) FindByID(id uint64) (n *types.Namespace, err error) { - var ( - sql string - args []interface{} - ) - - n = &types.Namespace{} - - qb := r.query(). - Columns(r.columns()...). - Where("id = ?", id) - - if sql, args, err = qb.ToSql(); err != nil { - return - } - - if err = r.db().Get(n, sql, args...); err != nil { - return - } - - if n == nil || n.ID == 0 { - return nil, ErrNamespaceNotFound - } - - return -} - -func (r *namespace) Find(filter types.NamespaceFilter) (nn types.NamespaceSet, f types.NamespaceFilter, err error) { - var ( - sql string - args []interface{} - ) - - f = filter - - if f.PerPage > 100 { - f.PerPage = 100 - } else if f.PerPage == 0 { - f.PerPage = 50 - } - - qb := r.query() - if f.Query != "" { - q := "%" + f.Query + "%" - qb = qb.Where("name like ? OR slug like ?", q, q) - } - - { - cq := qb.Column(squirrel.Alias(squirrel.Expr("COUNT(*)"), "count")) - if sql, args, err = cq.ToSql(); err != nil { - return - } - - if err = r.db().Get(&f.Count, sql, args...); err != nil { - return - } - - if f.Count == 0 { - // No rows with this filter no need to continue - return - } - } - - if f.Page > 0 { - qb = qb.Offset(uint64(f.PerPage * f.Page)) - } - - qb = qb. - Limit(uint64(f.PerPage)). - Columns(r.columns()...). - OrderBy("id ASC") - - if sql, args, err = qb.ToSql(); err != nil { - return - } - - if err = r.db().Select(&nn, sql, args...); err != nil { - return - } - - return -} - func (r namespace) query() squirrel.SelectBuilder { return squirrel. Select(). @@ -148,6 +59,49 @@ func (r namespace) query() squirrel.SelectBuilder { } +func (r *namespace) With(ctx context.Context, db *factory.DB) NamespaceRepository { + return &namespace{ + repository: r.repository.With(ctx, db), + } +} + +func (r *namespace) FindByID(namespaceID uint64) (*types.Namespace, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("id = ?", namespaceID) + + n = &types.Namespace{} + ) + + return n, isFound(r.fetchOne(n, query), n.ID > 0, ErrNamespaceNotFound) +} + +func (r *namespace) Find(filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error) { + f = filter + f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50) + + query := r.query() + if f.Query != "" { + q := "%" + f.Query + "%" + query = query.Where("name like ? OR slug like ?", q, q) + } + + if f.Count, err = r.count(query); err != nil || f.Count == 0 { + return + } + + if f.Page > 0 { + query = query.Offset(uint64(f.PerPage * f.Page)) + } + + query = query. + Columns(r.columns()...). + OrderBy("id ASC") + + return set, f, r.fetchPaged(&set, query, f.Page, f.PerPage) +} + func (r *namespace) Create(mod *types.Namespace) (*types.Namespace, error) { mod.ID = factory.Sonyflake.NextID() mod.CreatedAt = time.Now() @@ -163,6 +117,6 @@ func (r *namespace) Update(mod *types.Namespace) (*types.Namespace, error) { } func (r *namespace) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM compose_namespace WHERE id=?", id) + _, err := r.db().Exec("DELETE FROM "+r.table()+" WHERE id=?", id) return err } diff --git a/compose/internal/repository/repository.go b/compose/internal/repository/repository.go index fd7418b90..547d6d687 100644 --- a/compose/internal/repository/repository.go +++ b/compose/internal/repository/repository.go @@ -3,7 +3,9 @@ package repository import ( "context" + "github.com/pkg/errors" "github.com/titpetric/factory" + "gopkg.in/Masterminds/squirrel.v1" "github.com/crusttech/crust/internal/auth" ) @@ -45,3 +47,78 @@ func (r *repository) db() *factory.DB { } return DB(r.ctx) } + +// Fetches single row from table +func (r repository) fetchOne(one interface{}, q squirrel.SelectBuilder) (err error) { + var ( + sql string + args []interface{} + ) + + if sql, args, err = q.ToSql(); err != nil { + return + } + + if err = r.db().Get(one, sql, args...); err != nil { + return + } + + return +} + +// Counts all rows that match conditions from given query builder +func (r repository) count(q squirrel.SelectBuilder) (uint, error) { + var ( + count uint + cq = q.Column("COUNT(*)") + ) + + if sqlSelect, argsSelect, err := cq.ToSql(); err != nil { + return 0, err + } else { + if err := r.db().Get(&count, sqlSelect, argsSelect...); err != nil { + return 0, err + } + } + + return count, nil +} + +// Fetches paged rows +func (r repository) fetchPaged(set interface{}, q squirrel.SelectBuilder, page, perPage uint) error { + q = q. + Limit(uint64(perPage)). + Offset(uint64(page * perPage)) + + if sqlSelect, argsSelect, err := q.ToSql(); err != nil { + return err + } else { + return r.db().Select(set, sqlSelect, argsSelect...) + } +} + +func normalizePerPage(val, min, max, def uint) uint { + if val == 0 { + return def + } + + if val > max { + return max + } + + if val < min { + return min + } + + return val +} + +func isFound(err error, valid bool, nerr error) error { + if err != nil { + return errors.WithStack(err) + } else if !valid { + return errors.WithStack(nerr) + } + + return nil +} diff --git a/compose/internal/service/attachment.go b/compose/internal/service/attachment.go index 7b7af28a5..c19057b8d 100644 --- a/compose/internal/service/attachment.go +++ b/compose/internal/service/attachment.go @@ -48,12 +48,13 @@ type ( AttachmentService interface { With(ctx context.Context) AttachmentService - FindByID(id uint64) (*types.Attachment, error) + FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error) Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) - CreatePageAttachment(name string, size int64, fh io.ReadSeeker, pageID uint64) (*types.Attachment, error) - CreateRecordAttachment(name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (*types.Attachment, error) + CreatePageAttachment(namespaceID uint64, name string, size int64, fh io.ReadSeeker, pageID uint64) (*types.Attachment, error) + CreateRecordAttachment(namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (*types.Attachment, error) OpenOriginal(att *types.Attachment) (io.ReadSeeker, error) OpenPreview(att *types.Attachment) (io.ReadSeeker, error) + DeleteByID(namespaceID, attachmentID uint64) error } ) @@ -68,7 +69,7 @@ func Attachment(store store.Store) AttachmentService { }).With(context.Background()) } -func (svc *attachment) With(ctx context.Context) AttachmentService { +func (svc attachment) With(ctx context.Context) AttachmentService { db := repository.DB(ctx) return &attachment{ db: db, @@ -85,11 +86,27 @@ func (svc *attachment) With(ctx context.Context) AttachmentService { } } -func (svc *attachment) FindByID(id uint64) (*types.Attachment, error) { - return svc.attachment.FindByID(id) +func (svc attachment) FindByID(namespaceID, attachmentID uint64) (*types.Attachment, error) { + if namespaceID == 0 { + return nil, ErrNamespaceRequired + } + + return svc.attachment.FindByID(namespaceID, attachmentID) } -func (svc *attachment) Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) { +func (svc attachment) DeleteByID(namespaceID, attachmentID uint64) error { + if namespaceID == 0 { + return ErrNamespaceRequired + } + + return svc.attachment.DeleteByID(namespaceID, attachmentID) +} + +func (svc attachment) Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) { + if filter.NamespaceID == 0 { + return nil, filter, ErrNamespaceRequired + } + if filter.PageID > 0 { if _, err := svc.pageSvc.FindByID(filter.PageID); err != nil { return nil, filter, err @@ -111,7 +128,7 @@ func (svc *attachment) Find(filter types.AttachmentFilter) (types.AttachmentSet, return svc.attachment.Find(filter) } -func (svc *attachment) OpenOriginal(att *types.Attachment) (io.ReadSeeker, error) { +func (svc attachment) OpenOriginal(att *types.Attachment) (io.ReadSeeker, error) { if len(att.Url) == 0 { return nil, nil } @@ -119,7 +136,7 @@ func (svc *attachment) OpenOriginal(att *types.Attachment) (io.ReadSeeker, error return svc.store.Open(att.Url) } -func (svc *attachment) OpenPreview(att *types.Attachment) (io.ReadSeeker, error) { +func (svc attachment) OpenPreview(att *types.Attachment) (io.ReadSeeker, error) { if len(att.PreviewUrl) == 0 { return nil, nil } @@ -127,7 +144,11 @@ func (svc *attachment) OpenPreview(att *types.Attachment) (io.ReadSeeker, error) return svc.store.Open(att.PreviewUrl) } -func (svc *attachment) CreatePageAttachment(name string, size int64, fh io.ReadSeeker, pageID uint64) (*types.Attachment, error) { +func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size int64, fh io.ReadSeeker, pageID uint64) (*types.Attachment, error) { + if namespaceID == 0 { + return nil, ErrNamespaceRequired + } + var currentUserID uint64 = auth.GetIdentityFromContext(svc.ctx).Identity() if p, err := svc.pageSvc.FindByID(pageID); err != nil { @@ -137,15 +158,20 @@ func (svc *attachment) CreatePageAttachment(name string, size int64, fh io.ReadS } att := &types.Attachment{ - ID: factory.Sonyflake.NextID(), - OwnerID: currentUserID, - Name: strings.TrimSpace(name), - Kind: types.PageAttachment, + ID: factory.Sonyflake.NextID(), + NamespaceID: namespaceID, + OwnerID: currentUserID, + Name: strings.TrimSpace(name), + Kind: types.PageAttachment, } return att, svc.create(name, size, fh, att) } -func (svc *attachment) CreateRecordAttachment(name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (*types.Attachment, error) { +func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (*types.Attachment, error) { + if namespaceID == 0 { + return nil, ErrNamespaceRequired + } + var currentUserID uint64 = auth.GetIdentityFromContext(svc.ctx).Identity() if _, err := svc.moduleSvc.FindByID(moduleID); err != nil { @@ -157,16 +183,17 @@ func (svc *attachment) CreateRecordAttachment(name string, size int64, fh io.Rea } att := &types.Attachment{ - ID: factory.Sonyflake.NextID(), - OwnerID: currentUserID, - Name: strings.TrimSpace(name), - Kind: types.RecordAttachment, + ID: factory.Sonyflake.NextID(), + NamespaceID: namespaceID, + OwnerID: currentUserID, + Name: strings.TrimSpace(name), + Kind: types.RecordAttachment, } return att, svc.create(name, size, fh, att) } -func (svc *attachment) create(name string, size int64, fh io.ReadSeeker, att *types.Attachment) (err error) { +func (svc attachment) create(name string, size int64, fh io.ReadSeeker, att *types.Attachment) (err error) { if svc.store == nil { return errors.New("Can not create attachment: store handler not set") } @@ -205,7 +232,7 @@ func (svc *attachment) create(name string, size int64, fh io.ReadSeeker, att *ty }) } -func (svc *attachment) extractMimetype(file io.ReadSeeker) (mimetype string, err error) { +func (svc attachment) extractMimetype(file io.ReadSeeker) (mimetype string, err error) { if _, err = file.Seek(0, 0); err != nil { return } @@ -222,7 +249,7 @@ func (svc *attachment) extractMimetype(file io.ReadSeeker) (mimetype string, err return http.DetectContentType(buf), nil } -func (svc *attachment) processImage(original io.ReadSeeker, att *types.Attachment) (err error) { +func (svc attachment) processImage(original io.ReadSeeker, att *types.Attachment) (err error) { if !strings.HasPrefix(att.Meta.Original.Mimetype, "image/") { // Only supporting previews from images (for now) return diff --git a/compose/internal/service/error.go b/compose/internal/service/error.go index ae7b9751a..f453ca23f 100644 --- a/compose/internal/service/error.go +++ b/compose/internal/service/error.go @@ -15,6 +15,7 @@ const ( ErrNoReadPermissions serviceError = "NoReadPermissions" ErrNoUpdatePermissions serviceError = "NoUpdatePermissions" ErrNoDeletePermissions serviceError = "NoDeletePermissions" + ErrNamespaceRequired serviceError = "NamespaceRequired" ErrNotImplemented serviceError = "NotImplemented" ) diff --git a/compose/internal/service/namespace.go b/compose/internal/service/namespace.go index e544c49fd..13a36c0f6 100644 --- a/compose/internal/service/namespace.go +++ b/compose/internal/service/namespace.go @@ -2,7 +2,6 @@ package service import ( "context" - "time" "github.com/titpetric/factory" @@ -64,17 +63,17 @@ func (svc *namespace) FindByID(ID uint64) (n *types.Namespace, err error) { return } -func (svc *namespace) Find(filter types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) { - nn, f, err := svc.namespaceRepo.Find(filter) +func (svc *namespace) Find(filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error) { + set, f, err = svc.namespaceRepo.Find(filter) if err != nil { - return nil, f, err + return } - nn, _ = nn.Filter(func(m *types.Namespace) (bool, error) { + set, _ = set.Filter(func(m *types.Namespace) (bool, error) { return svc.prmSvc.CanReadNamespace(m), nil }) - return nn, f, nil + return } func (svc *namespace) Create(mod *types.Namespace) (*types.Namespace, error) { @@ -85,24 +84,28 @@ func (svc *namespace) Create(mod *types.Namespace) (*types.Namespace, error) { return svc.namespaceRepo.Create(mod) } -func (svc *namespace) Update(updated *types.Namespace) (m *types.Namespace, err error) { - m, err = svc.FindByID(updated.ID) +func (svc *namespace) Update(mod *types.Namespace) (m *types.Namespace, err error) { + if mod.ID == 0 { + return nil, ErrInvalidID.withStack() + } + + m, err = svc.FindByID(mod.ID) if err != nil { return nil, err } - if isStale(updated.UpdatedAt, m.UpdatedAt, m.CreatedAt) { - return nil, ErrStaleData + if isStale(mod.UpdatedAt, m.UpdatedAt, m.CreatedAt) { + return nil, ErrStaleData.withStack() } if !svc.prmSvc.CanUpdateNamespace(m) { return nil, ErrNoUpdatePermissions.withStack() } - m.Name = updated.Name - m.Slug = updated.Slug - m.Meta = updated.Meta - m.Enabled = updated.Enabled + m.Name = mod.Name + m.Slug = mod.Slug + m.Meta = mod.Meta + m.Enabled = mod.Enabled return svc.namespaceRepo.Update(m) } @@ -116,17 +119,3 @@ func (svc *namespace) DeleteByID(ID uint64) error { return svc.namespaceRepo.DeleteByID(ID) } - -// Data is stale when new date does not match updatedAt or createdAt (before first update) -func isStale(new *time.Time, updatedAt *time.Time, createdAt time.Time) bool { - - if new == nil { - return false - } - - if updatedAt != nil { - return !new.Equal(*updatedAt) - } - - return new.Equal(createdAt) -} diff --git a/compose/internal/service/service.go b/compose/internal/service/service.go index 7a1416380..a6657f45c 100644 --- a/compose/internal/service/service.go +++ b/compose/internal/service/service.go @@ -1,6 +1,8 @@ package service import ( + "time" + "github.com/crusttech/crust/internal/store" ) @@ -40,3 +42,17 @@ func Init() error { return nil } + +// Data is stale when new date does not match updatedAt or createdAt (before first update) +func isStale(new *time.Time, updatedAt *time.Time, createdAt time.Time) bool { + if new == nil { + // Change to true to require stale-data-check. + return true + } + + if updatedAt != nil { + return !new.Equal(*updatedAt) + } + + return new.Equal(createdAt) +} diff --git a/compose/rest/attachment.go b/compose/rest/attachment.go index b6417e1bd..0eee17bcb 100644 --- a/compose/rest/attachment.go +++ b/compose/rest/attachment.go @@ -6,7 +6,8 @@ import ( "io" "net/http" "net/url" - "time" + + "github.com/titpetric/factory/resputil" "github.com/crusttech/crust/compose/internal/service" "github.com/crusttech/crust/compose/rest/request" @@ -20,14 +21,12 @@ var _ = errors.Wrap type ( attachmentPayload struct { - ID uint64 `json:"attachmentID,string"` - OwnerID uint64 `json:"ownerID,string"` - Url string `json:"url"` - PreviewUrl string `json:"previewUrl,omitempty"` - Meta interface{} `json:"meta"` - Name string `json:"name"` - CreatedAt time.Time `json:"createdAt,omitempty"` - UpdatedAt *time.Time `json:"updatedAt,omitempty"` + *types.Attachment + } + + attachmentSetPayload struct { + Filter types.AttachmentFilter `json:"filter"` + Set []*attachmentPayload `json:"set"` } Attachment struct { @@ -36,11 +35,13 @@ type ( ) func (Attachment) New() *Attachment { - return &Attachment{attachment: service.DefaultAttachment} + return &Attachment{ + attachment: service.DefaultAttachment, + } } // Attachments returns list of all files attached to records -func (ctrl *Attachment) List(ctx context.Context, r *request.AttachmentList) (interface{}, error) { +func (ctrl Attachment) List(ctx context.Context, r *request.AttachmentList) (interface{}, error) { f := types.AttachmentFilter{ NamespaceID: r.NamespaceID, Kind: r.Kind, @@ -53,44 +54,41 @@ func (ctrl *Attachment) List(ctx context.Context, r *request.AttachmentList) (in // Sort: r.Sort, } - aa, meta, err := ctrl.attachment.Find(f) + set, filter, err := ctrl.attachment.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, set, filter, err) +} + +func (ctrl Attachment) Read(ctx context.Context, r *request.AttachmentRead) (interface{}, error) { + a, err := ctrl.attachment.FindByID(r.NamespaceID, r.AttachmentID) + return makeAttachmentPayload(ctx, a, err) +} + +func (ctrl Attachment) Delete(ctx context.Context, r *request.AttachmentDelete) (interface{}, error) { + _, err := ctrl.attachment.FindByID(r.NamespaceID, r.AttachmentID) if err != nil { return nil, err } - pp := make([]*attachmentPayload, len(aa)) - for i := range aa { - pp[i] = makeAttachmentPayload(aa[i], auth.GetIdentityFromContext(ctx).Identity()) - } - - return map[string]interface{}{"meta": meta, "attachments": pp}, nil -} - -func (ctrl Attachment) Details(ctx context.Context, r *request.AttachmentDetails) (interface{}, error) { - if a, err := ctrl.attachment.FindByID(r.AttachmentID); err != nil { - return nil, err - } else { - return makeAttachmentPayload(a, auth.GetIdentityFromContext(ctx).Identity()), nil - } + return resputil.OK(), ctrl.attachment.With(ctx).DeleteByID(r.NamespaceID, r.AttachmentID) } func (ctrl Attachment) Original(ctx context.Context, r *request.AttachmentOriginal) (interface{}, error) { - if err := ctrl.isAccessible(r.AttachmentID, r.UserID, r.Sign); err != nil { + if err := ctrl.isAccessible(r.NamespaceID, r.AttachmentID, r.UserID, r.Sign); err != nil { return nil, err } - return ctrl.serve(ctx, r.AttachmentID, false, r.Download) + return ctrl.serve(ctx, r.NamespaceID, r.AttachmentID, false, r.Download) } -func (ctrl *Attachment) Preview(ctx context.Context, r *request.AttachmentPreview) (interface{}, error) { - if err := ctrl.isAccessible(r.AttachmentID, r.UserID, r.Sign); err != nil { +func (ctrl Attachment) Preview(ctx context.Context, r *request.AttachmentPreview) (interface{}, error) { + if err := ctrl.isAccessible(r.NamespaceID, r.AttachmentID, r.UserID, r.Sign); err != nil { return nil, err } - return ctrl.serve(ctx, r.AttachmentID, true, false) + return ctrl.serve(ctx, r.NamespaceID, r.AttachmentID, true, false) } -func (ctrl Attachment) isAccessible(attachmentID, userID uint64, signature string) error { +func (ctrl Attachment) isAccessible(namespaceID, attachmentID, userID uint64, signature string) error { if userID == 0 { return errors.New("missing or invalid user ID") } @@ -99,16 +97,16 @@ func (ctrl Attachment) isAccessible(attachmentID, userID uint64, signature strin return errors.New("missing or invalid attachment ID") } - if auth.DefaultSigner.Verify(signature, userID, attachmentID) { + if auth.DefaultSigner.Verify(signature, userID, namespaceID, attachmentID) { return errors.New("missing or invalid signature") } return nil } -func (ctrl Attachment) serve(ctx context.Context, ID uint64, preview, download bool) (interface{}, error) { +func (ctrl Attachment) serve(ctx context.Context, namespaceID, attachmentID uint64, preview, download bool) (interface{}, error) { return func(w http.ResponseWriter, req *http.Request) { - att, err := ctrl.attachment.With(ctx).FindByID(ID) + att, err := ctrl.attachment.With(ctx).FindByID(namespaceID, attachmentID) if err != nil { // Simplify error handling for now w.WriteHeader(http.StatusNotFound) @@ -140,17 +138,33 @@ func (ctrl Attachment) serve(ctx context.Context, ID uint64, preview, download b }, nil } -func makeAttachmentPayload(a *types.Attachment, userID uint64) *attachmentPayload { - if a == nil { - return nil +func (ctrl Attachment) makeFilterPayload(ctx context.Context, aa types.AttachmentSet, f types.AttachmentFilter, err error) (*attachmentSetPayload, error) { + if err != nil { + return nil, err + } + + asp := &attachmentSetPayload{Filter: f, Set: make([]*attachmentPayload, len(aa))} + + for i := range aa { + asp.Set[i], _ = makeAttachmentPayload(ctx, aa[i], nil) + } + + return asp, nil +} + +func makeAttachmentPayload(ctx context.Context, a *types.Attachment, err error) (*attachmentPayload, error) { + if err != nil || a == nil { + return nil, err } var ( - signParams = fmt.Sprintf("?sign=%s&userID=%d", auth.DefaultSigner.Sign(userID, a.ID), userID) + userID = auth.GetIdentityFromContext(ctx).Identity() + signParams = fmt.Sprintf("?sign=%s&userID=%d", auth.DefaultSigner.Sign(userID, a.NamespaceID, a.ID), userID) preview string - baseURL = fmt.Sprintf("/attachment/%s/%d/", a.Kind, a.ID) + baseURL = fmt.Sprintf("/namespace/%d/attachment/%s/%d/", a.NamespaceID, a.Kind, a.ID) ) + if a.Meta.Preview != nil { var ext = a.Meta.Preview.Extension if ext == "" { @@ -160,14 +174,10 @@ func makeAttachmentPayload(a *types.Attachment, userID uint64) *attachmentPayloa preview = baseURL + fmt.Sprintf("preview.%s", ext) } - return &attachmentPayload{ - ID: a.ID, - OwnerID: a.OwnerID, - Url: baseURL + fmt.Sprintf("original/%s", url.PathEscape(a.Name)) + signParams, - PreviewUrl: preview + signParams, - Meta: a.Meta, - Name: a.Name, - CreatedAt: a.CreatedAt, - UpdatedAt: a.UpdatedAt, - } + ap := &attachmentPayload{a} + + ap.Url = baseURL + fmt.Sprintf("original/%s", url.PathEscape(a.Name)) + signParams + ap.PreviewUrl = preview + signParams + + return ap, nil } diff --git a/compose/rest/handlers/attachment.go b/compose/rest/handlers/attachment.go index e2e641e1e..453e4106e 100644 --- a/compose/rest/handlers/attachment.go +++ b/compose/rest/handlers/attachment.go @@ -29,7 +29,8 @@ import ( // Internal API interface type AttachmentAPI interface { List(context.Context, *request.AttachmentList) (interface{}, error) - Details(context.Context, *request.AttachmentDetails) (interface{}, error) + Read(context.Context, *request.AttachmentRead) (interface{}, error) + Delete(context.Context, *request.AttachmentDelete) (interface{}, error) Original(context.Context, *request.AttachmentOriginal) (interface{}, error) Preview(context.Context, *request.AttachmentPreview) (interface{}, error) } @@ -37,7 +38,8 @@ type AttachmentAPI interface { // HTTP API interface type Attachment struct { List func(http.ResponseWriter, *http.Request) - Details func(http.ResponseWriter, *http.Request) + Read func(http.ResponseWriter, *http.Request) + Delete func(http.ResponseWriter, *http.Request) Original func(http.ResponseWriter, *http.Request) Preview func(http.ResponseWriter, *http.Request) } @@ -64,14 +66,34 @@ func NewAttachment(ah AttachmentAPI) *Attachment { return } }, - Details: func(w http.ResponseWriter, r *http.Request) { + Read: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewAttachmentDetails() + params := request.NewAttachmentRead() if err := params.Fill(r); err != nil { resputil.JSON(w, err) return } - if value, err := ah.Details(r.Context(), params); err != nil { + if value, err := ah.Read(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + Delete: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewAttachmentDelete() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := ah.Delete(r.Context(), params); err != nil { resputil.JSON(w, err) return } else { @@ -131,7 +153,8 @@ func (ah *Attachment) MountRoutes(r chi.Router, middlewares ...func(http.Handler r.Group(func(r chi.Router) { r.Use(middlewares...) r.Get("/namespace/{namespaceID}/attachment/{kind}/", ah.List) - r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}", ah.Details) + r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}", ah.Read) + r.Delete("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}", ah.Delete) r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/original/{name}", ah.Original) r.Get("/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/preview.{ext}", ah.Preview) }) diff --git a/compose/rest/namespace.go b/compose/rest/namespace.go index 2c693756d..b1767638d 100644 --- a/compose/rest/namespace.go +++ b/compose/rest/namespace.go @@ -47,8 +47,8 @@ func (ctrl Namespace) List(ctx context.Context, r *request.NamespaceList) (inter Page: r.Page, } - nn, filter, err := ctrl.namespace.With(ctx).Find(f) - return ctrl.makeFilterPayload(ctx, nn, filter, err) + set, filter, err := ctrl.namespace.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, set, filter, err) } func (ctrl Namespace) Create(ctx context.Context, r *request.NamespaceCreate) (interface{}, error) { @@ -92,16 +92,11 @@ func (ctrl Namespace) Delete(ctx context.Context, r *request.NamespaceDelete) (i return nil, err } - err = ctrl.namespace.With(ctx).DeleteByID(r.NamespaceID) - if err != nil { - return nil, err - } else { - return resputil.Success("deleted"), nil - } + return resputil.OK(), ctrl.namespace.With(ctx).DeleteByID(r.NamespaceID) } func (ctrl Namespace) makePayload(ctx context.Context, ns *types.Namespace, err error) (*namespacePayload, error) { - if err != nil { + if err != nil || ns == nil { return nil, err } diff --git a/compose/rest/page.go b/compose/rest/page.go index 4d42aed83..8eb346c24 100644 --- a/compose/rest/page.go +++ b/compose/rest/page.go @@ -8,7 +8,6 @@ import ( "github.com/crusttech/crust/compose/internal/service" "github.com/crusttech/crust/compose/rest/request" "github.com/crusttech/crust/compose/types" - "github.com/crusttech/crust/internal/auth" "github.com/crusttech/crust/internal/payload" ) @@ -85,15 +84,12 @@ func (ctrl *Page) Upload(ctx context.Context, r *request.PageUpload) (interface{ defer file.Close() a, err := ctrl.attachment.With(ctx).CreatePageAttachment( + r.NamespaceID, r.Upload.Filename, r.Upload.Size, file, r.PageID, ) - if err != nil { - return nil, err - } - - return makeAttachmentPayload(a, auth.GetIdentityFromContext(ctx).Identity()), nil + return makeAttachmentPayload(ctx, a, err) } diff --git a/compose/rest/record.go b/compose/rest/record.go index c565b22be..13f40e39a 100644 --- a/compose/rest/record.go +++ b/compose/rest/record.go @@ -5,12 +5,11 @@ import ( "github.com/titpetric/factory/resputil" + "github.com/pkg/errors" + "github.com/crusttech/crust/compose/internal/service" "github.com/crusttech/crust/compose/rest/request" "github.com/crusttech/crust/compose/types" - "github.com/crusttech/crust/internal/auth" - - "github.com/pkg/errors" ) var _ = errors.Wrap @@ -64,6 +63,7 @@ func (ctrl *Record) Upload(ctx context.Context, r *request.RecordUpload) (interf defer file.Close() a, err := ctrl.attachment.With(ctx).CreateRecordAttachment( + r.NamespaceID, r.Upload.Filename, r.Upload.Size, file, @@ -72,9 +72,5 @@ func (ctrl *Record) Upload(ctx context.Context, r *request.RecordUpload) (interf r.FieldName, ) - if err != nil { - return nil, err - } - - return makeAttachmentPayload(a, auth.GetIdentityFromContext(ctx).Identity()), nil + return makeAttachmentPayload(ctx, a, err) } diff --git a/compose/rest/request/attachment.go b/compose/rest/request/attachment.go index 7325e683d..fe361837e 100644 --- a/compose/rest/request/attachment.go +++ b/compose/rest/request/attachment.go @@ -115,8 +115,8 @@ func (aReq *AttachmentList) Fill(r *http.Request) (err error) { var _ RequestFiller = NewAttachmentList() -// Attachment details request parameters -type AttachmentDetails struct { +// Attachment read request parameters +type AttachmentRead struct { AttachmentID uint64 `json:",string"` Kind string NamespaceID uint64 `json:",string"` @@ -124,11 +124,11 @@ type AttachmentDetails struct { UserID uint64 `json:",string"` } -func NewAttachmentDetails() *AttachmentDetails { - return &AttachmentDetails{} +func NewAttachmentRead() *AttachmentRead { + return &AttachmentRead{} } -func (aReq *AttachmentDetails) Fill(r *http.Request) (err error) { +func (aReq *AttachmentRead) Fill(r *http.Request) (err error) { if strings.ToLower(r.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(r.Body).Decode(aReq) @@ -170,7 +170,64 @@ func (aReq *AttachmentDetails) Fill(r *http.Request) (err error) { return err } -var _ RequestFiller = NewAttachmentDetails() +var _ RequestFiller = NewAttachmentRead() + +// Attachment delete request parameters +type AttachmentDelete struct { + AttachmentID uint64 `json:",string"` + Kind string + NamespaceID uint64 `json:",string"` + Sign string + UserID uint64 `json:",string"` +} + +func NewAttachmentDelete() *AttachmentDelete { + return &AttachmentDelete{} +} + +func (aReq *AttachmentDelete) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(aReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + aReq.AttachmentID = parseUInt64(chi.URLParam(r, "attachmentID")) + aReq.Kind = chi.URLParam(r, "kind") + aReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) + if val, ok := get["sign"]; ok { + + aReq.Sign = val + } + if val, ok := get["userID"]; ok { + + aReq.UserID = parseUInt64(val) + } + + return err +} + +var _ RequestFiller = NewAttachmentDelete() // Attachment original request parameters type AttachmentOriginal struct { diff --git a/compose/types/attachment.go b/compose/types/attachment.go index 61982617d..f23028e75 100644 --- a/compose/types/attachment.go +++ b/compose/types/attachment.go @@ -10,30 +10,34 @@ import ( type ( Attachment struct { - ID uint64 `db:"id" json:"attachmentID,omitempty"` - OwnerID uint64 `db:"rel_owner" json:"ownerID,omitempty"` - Kind string `db:"kind" json:"-"` - Url string `db:"url" json:"url,omitempty"` - PreviewUrl string `db:"preview_url"json:"previewUrl,omitempty"` - Name string `db:"name" json:"name,omitempty"` - Meta attachmentMeta `db:"meta" json:"meta"` - CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` - UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"` - DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` + ID uint64 `db:"id" json:"attachmentID,string"` + OwnerID uint64 `db:"rel_owner" json:"ownerID,string"` + Kind string `db:"kind" json:"-"` + Url string `db:"url" json:"url,omitempty"` + PreviewUrl string `db:"preview_url" json:"previewUrl,omitempty"` + Name string `db:"name" json:"name,omitempty"` + Meta attachmentMeta `db:"meta" json:"meta"` + + NamespaceID uint64 `db:"rel_namespace" json:"namespaceID,string"` + + CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` + UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"` + DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` } // AttachmentFilter is used for filtering and as a return value from Find AttachmentFilter struct { - Kind string `json:"kind,omitempty"` - PageID uint64 `json:"pageID,string,omitempty"` - RecordID uint64 `json:"recordID,string,omitempty"` - ModuleID uint64 `json:"moduleID,string,omitempty"` - FieldName string `json:"fieldName,omitempty"` - Filter string `json:"filter"` - Page uint `json:"page"` - PerPage uint `json:"perPage"` - Sort string `json:"sort"` - Count uint `json:"count"` + NamespaceID uint64 `json:"namespaceID,string"` + Kind string `json:"kind,omitempty"` + PageID uint64 `json:"pageID,string,omitempty"` + RecordID uint64 `json:"recordID,string,omitempty"` + ModuleID uint64 `json:"moduleID,string,omitempty"` + FieldName string `json:"fieldName,omitempty"` + Filter string `json:"filter"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + Sort string `json:"sort"` + Count uint `json:"count"` } attachmentImageMeta struct { diff --git a/compose/types/chart.go b/compose/types/chart.go index 03a44486f..0832a8e43 100644 --- a/compose/types/chart.go +++ b/compose/types/chart.go @@ -14,10 +14,20 @@ type ( Name string `json:"name" db:"name"` Config types.JSONText `json:"config" db:"config"` + NamespaceID uint64 `json:"namespaceID,string" db:"rel_namespace,string"` + CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"` DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` } + + ChartFilter struct { + Query string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + Sort string `json:"sort"` + Count uint `json:"count"` + } ) // Resource returns a system resource ID for this type diff --git a/compose/types/module.go b/compose/types/module.go index 1a73f0d94..06590f390 100644 --- a/compose/types/module.go +++ b/compose/types/module.go @@ -16,6 +16,8 @@ type ( Fields ModuleFieldSet `json:"fields" db:"-"` Page *Page `json:"page,omitempty"` + NamespaceID uint64 `json:"namespaceID,string" db:"rel_namespace"` + CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"` DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` diff --git a/compose/types/namespace.go b/compose/types/namespace.go index ef1390ef7..70439e26e 100644 --- a/compose/types/namespace.go +++ b/compose/types/namespace.go @@ -10,15 +10,15 @@ import ( type ( Namespace struct { - ID uint64 `json:"namespaceID,string" db:"id"` - Name string `json:"name" db:"name"` - Slug string `json:"slug" db:"slug"` - Enabled bool `json:"enabled" db:"enabled"` - Meta types.JSONText `json:"meta" db:"meta"` + ID uint64 `db:"id" json:"namespaceID,string"` + Name string `db:"name" json:"name"` + Slug string `db:"slug" json:"slug"` + Enabled bool `db:"enabled" json:"enabled"` + Meta types.JSONText `db:"meta" json:"meta"` - CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"` - UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at"` - DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"` + CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` + UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"` + DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` } NamespaceFilter struct { @@ -31,7 +31,7 @@ type ( ) const ( - NamespaceCRM uint64 = 10000000 + NamespaceCRM uint64 = 88714882739863655 ) // Resource returns a system resource ID for this type diff --git a/compose/types/page.go b/compose/types/page.go index 1a20ac8e7..b0fdabbb8 100644 --- a/compose/types/page.go +++ b/compose/types/page.go @@ -12,6 +12,8 @@ type ( ID uint64 `json:"pageID,string" db:"id"` SelfID uint64 `json:"selfID,string" db:"self_id"` + NamespaceID uint64 `json:"namespaceID,string" db:"rel_namespace"` + ModuleID uint64 `json:"moduleID,string" db:"module_id"` Module *Module `json:"module,omitempty" db:"-"` diff --git a/compose/types/record.go b/compose/types/record.go index e542bc3c3..7517d1697 100644 --- a/compose/types/record.go +++ b/compose/types/record.go @@ -14,6 +14,8 @@ type ( Values RecordValueSet `json:"values,omitempty" db:"-"` + NamespaceID uint64 `json:"namespaceID,string", db:"rel_namespace"` + OwnedBy uint64 `db:"owned_by" json:"ownedBy,string"` CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` CreatedBy uint64 `db:"created_by" json:"createdBy,string" ` diff --git a/compose/types/trigger.go b/compose/types/trigger.go index 3baf2916c..59a389be3 100644 --- a/compose/types/trigger.go +++ b/compose/types/trigger.go @@ -18,6 +18,8 @@ type ( Enabled bool `json:"enabled" db:"enabled"` Source string `json:"source" db:"source"` + NamespaceID uint64 `json:"namespaceID,string" db:"rel_namespace"` + CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"` DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` diff --git a/docs/compose/README.md b/docs/compose/README.md index cde1eb7c8..5e81254c1 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -4,6 +4,7 @@ | ------ | -------- | ------- | | `GET` | `/namespace/{namespaceID}/attachment/{kind}/` | List, filter all page attachments | | `GET` | `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}` | Attachment details | +| `DELETE` | `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}` | Delete attachment | | `GET` | `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/original/{name}` | Serves attached file | | `GET` | `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}/preview.{ext}` | Serves preview of an attached file | @@ -20,7 +21,7 @@ | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | pageID | uint64 | GET | Filter attachments by page ID | N/A | NO | -| moduleID | uint64 | GET | Filter attachments by mnodule ID | N/A | NO | +| moduleID | uint64 | GET | Filter attachments by module ID | N/A | NO | | recordID | uint64 | GET | Filter attachments by record ID | N/A | NO | | fieldName | string | GET | Filter attachments by field name | N/A | NO | | page | uint | GET | Page number (0 based) | N/A | NO | @@ -48,6 +49,24 @@ | sign | string | GET | Signature | N/A | YES | | userID | uint64 | GET | User ID | N/A | YES | +## Delete attachment + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/{namespaceID}/attachment/{kind}/{attachmentID}` | HTTP/S | DELETE | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | +| kind | string | PATH | Attachment kind | N/A | YES | +| namespaceID | uint64 | PATH | Namespace ID | N/A | YES | +| sign | string | GET | Signature | N/A | YES | +| userID | uint64 | GET | User ID | N/A | YES | + ## Serves attached file #### Method From a41e4bfc19a4c70e835c3a11fc015c0e141bfe01 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sun, 28 Apr 2019 13:43:26 +0200 Subject: [PATCH 05/14] Apply namespace changes to all layers of charts --- api/compose/spec.json | 43 ++++++++-- api/compose/spec/chart.json | 40 +++++++-- api/compose/spec/namespace.json | 2 +- compose/internal/repository/chart.go | 97 +++++++++++++++++----- compose/internal/service/chart.go | 90 ++++++++++---------- compose/rest/chart.go | 118 +++++++++++++++++++++------ compose/rest/request/chart.go | 23 ++++++ compose/types/chart.go | 11 +-- docs/compose/README.md | 22 +++-- 9 files changed, 324 insertions(+), 122 deletions(-) diff --git a/api/compose/spec.json b/api/compose/spec.json index 5a3c7f89d..66af9eb76 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -159,7 +159,7 @@ { "type": "*time.Time", "name": "updatedAt", - "required": true, + "required": false, "title": "Last update (or creation) date" } ] @@ -755,7 +755,8 @@ "struct": [ { "imports": [ - "sqlxTypes github.com/jmoiron/sqlx/types" + "sqlxTypes github.com/jmoiron/sqlx/types", + "time" ] } ], @@ -773,13 +774,35 @@ { "name": "list", "method": "GET", - "title": "List/read charts from module section", - "path": "/" + "title": "List/read charts", + "path": "/", + "parameters": { + "get": [ + { + "name": "query", + "required": false, + "title": "Search query to match against charts", + "type": "string" + }, + { + "name": "page", + "type": "uint", + "required": false, + "title": "Page number (0 based)" + }, + { + "name": "perPage", + "type": "uint", + "required": false, + "title": "Returned items per page (default 50)" + } + ] + } }, { "name": "create", "method": "POST", - "title": "List/read charts from module section", + "title": "List/read charts ", "path": "/", "parameters": { "post": [ @@ -801,7 +824,7 @@ { "name": "read", "method": "GET", - "title": "Read charts by ID from module section", + "title": "Read charts by ID", "path": "/{chartID}", "parameters": { "path": [ @@ -817,7 +840,7 @@ { "name": "update", "method": "POST", - "title": "Add/update charts in module section", + "title": "Add/update charts", "path": "/{chartID}", "parameters": { "path": [ @@ -840,6 +863,12 @@ "title": "Chart name", "type": "string", "required": true + }, + { + "type": "*time.Time", + "name": "updatedAt", + "required": false, + "title": "Last update (or creation) date" } ] } diff --git a/api/compose/spec/chart.json b/api/compose/spec/chart.json index efa46aded..d2e5dc902 100644 --- a/api/compose/spec/chart.json +++ b/api/compose/spec/chart.json @@ -4,7 +4,8 @@ "Struct": [ { "imports": [ - "sqlxTypes github.com/jmoiron/sqlx/types" + "sqlxTypes github.com/jmoiron/sqlx/types", + "time" ] } ], @@ -25,14 +26,35 @@ { "Name": "list", "Method": "GET", - "Title": "List/read charts from module section", + "Title": "List/read charts", "Path": "/", - "Parameters": null + "Parameters": { + "get": [ + { + "name": "query", + "required": false, + "title": "Search query to match against charts", + "type": "string" + }, + { + "name": "page", + "required": false, + "title": "Page number (0 based)", + "type": "uint" + }, + { + "name": "perPage", + "required": false, + "title": "Returned items per page (default 50)", + "type": "uint" + } + ] + } }, { "Name": "create", "Method": "POST", - "Title": "List/read charts from module section", + "Title": "List/read charts ", "Path": "/", "Parameters": { "post": [ @@ -54,7 +76,7 @@ { "Name": "read", "Method": "GET", - "Title": "Read charts by ID from module section", + "Title": "Read charts by ID", "Path": "/{chartID}", "Parameters": { "path": [ @@ -70,7 +92,7 @@ { "Name": "update", "Method": "POST", - "Title": "Add/update charts in module section", + "Title": "Add/update charts", "Path": "/{chartID}", "Parameters": { "path": [ @@ -93,6 +115,12 @@ "required": true, "title": "Chart name", "type": "string" + }, + { + "name": "updatedAt", + "required": false, + "title": "Last update (or creation) date", + "type": "*time.Time" } ] } diff --git a/api/compose/spec/namespace.json b/api/compose/spec/namespace.json index 3b8588e4f..bf994f511 100644 --- a/api/compose/spec/namespace.json +++ b/api/compose/spec/namespace.json @@ -133,7 +133,7 @@ }, { "name": "updatedAt", - "required": true, + "required": false, "title": "Last update (or creation) date", "type": "*time.Time" } diff --git a/compose/internal/repository/chart.go b/compose/internal/repository/chart.go index 9bd1c0db8..70d432dce 100644 --- a/compose/internal/repository/chart.go +++ b/compose/internal/repository/chart.go @@ -5,6 +5,7 @@ import ( "time" "github.com/titpetric/factory" + "gopkg.in/Masterminds/squirrel.v1" "github.com/crusttech/crust/compose/types" ) @@ -13,11 +14,11 @@ type ( ChartRepository interface { With(ctx context.Context, db *factory.DB) ChartRepository - FindByID(id uint64) (*types.Chart, error) - Find() (types.ChartSet, error) + FindByID(namespaceID, attachmentID uint64) (*types.Chart, error) + Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) Create(mod *types.Chart) (*types.Chart, error) Update(mod *types.Chart) (*types.Chart, error) - DeleteByID(id uint64) error + DeleteByID(namespaceID, attachmentID uint64) error } chart struct { @@ -25,48 +26,100 @@ type ( } ) -const sqlChartColumns = "id, name, config, " + - "created_at, updated_at, deleted_at" - -const sqlChartSelect = "SELECT " + sqlChartColumns + " FROM compose_chart" +const ( + ErrChartNotFound = repositoryError("ChartNotFound") +) func Chart(ctx context.Context, db *factory.DB) ChartRepository { return (&chart{}).With(ctx, db) } -func (r *chart) With(ctx context.Context, db *factory.DB) ChartRepository { +func (r chart) With(ctx context.Context, db *factory.DB) ChartRepository { return &chart{ repository: r.repository.With(ctx, db), } } -func (r *chart) FindByID(id uint64) (*types.Chart, error) { - mod := &types.Chart{} - if err := r.db().Get(mod, sqlChartSelect+" WHERE id = ?", id); err != nil { - return nil, err +func (r chart) table() string { + return "compose_chart" +} + +func (r chart) columns() []string { + return []string{ + "id", "rel_namespace", "name", "config", + "created_at", "updated_at", "deleted_at", } - return mod, nil } -func (r *chart) Find() (types.ChartSet, error) { - mod := types.ChartSet{} - return mod, r.db().Select(&mod, sqlChartSelect+" ORDER BY id ASC") +func (r chart) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table()). + Where("deleted_at IS NULL") + } -func (r *chart) Create(mod *types.Chart) (*types.Chart, error) { +func (r chart) FindByID(namespaceID, chartID uint64) (*types.Chart, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("id = ?", chartID) + + c = &types.Chart{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) + } + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrChartNotFound) +} + +func (r chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { + f = filter + f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50) + + query := r.query() + + if filter.NamespaceID > 0 { + query = query.Where("a.rel_namespace = ?", filter.NamespaceID) + } + + if f.Query != "" { + q := "%" + f.Query + "%" + query = query.Where("name like ?", q) + } + + if f.Count, err = r.count(query); err != nil || f.Count == 0 { + return + } + + query = query. + Columns(r.columns()...). + OrderBy("id ASC") + + return set, f, r.fetchPaged(&set, query, f.Page, f.PerPage) +} + +func (r chart) Create(mod *types.Chart) (*types.Chart, error) { mod.ID = factory.Sonyflake.NextID() mod.CreatedAt = time.Now() - return mod, r.db().Insert("compose_chart", mod) + return mod, r.db().Insert(r.table(), mod) } -func (r *chart) Update(mod *types.Chart) (*types.Chart, error) { +func (r chart) Update(mod *types.Chart) (*types.Chart, error) { now := time.Now() mod.UpdatedAt = &now - return mod, r.db().Replace("compose_chart", mod) + return mod, r.db().Replace(r.table(), mod) } -func (r *chart) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM compose_chart WHERE id = ?", id) +func (r chart) DeleteByID(namespaceID, attachmentID uint64) error { + _, err := r.db().Exec( + "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", + namespaceID, + attachmentID, + ) + return err } diff --git a/compose/internal/service/chart.go b/compose/internal/service/chart.go index c1684c0f3..60ab1c29a 100644 --- a/compose/internal/service/chart.go +++ b/compose/internal/service/chart.go @@ -3,7 +3,6 @@ package service import ( "context" - "github.com/pkg/errors" "github.com/titpetric/factory" "github.com/crusttech/crust/compose/internal/repository" @@ -23,12 +22,12 @@ type ( ChartService interface { With(ctx context.Context) ChartService - FindByID(chartID uint64) (*types.Chart, error) - Find() (types.ChartSet, error) + FindByID(namespaceID, chartID uint64) (*types.Chart, error) + Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) Create(chart *types.Chart) (*types.Chart, error) Update(chart *types.Chart) (*types.Chart, error) - DeleteByID(chartID uint64) error + DeleteByID(namespaceID, chartID uint64) error } ) @@ -50,73 +49,74 @@ func (svc *chart) With(ctx context.Context) ChartService { } } -func (svc *chart) FindByID(chartID uint64) (c *types.Chart, err error) { - if c, err = svc.chartRepo.FindByID(chartID); err != nil { +func (svc *chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err error) { + if namespaceID == 0 { + return nil, ErrNamespaceRequired + } + + if c, err = svc.chartRepo.FindByID(namespaceID, chartID); err != nil { return } else if !svc.prmSvc.CanReadChart(c) { - return nil, errors.New("not allowed to access this chart") + return nil, ErrNoReadPermissions.withStack() } return } -func (svc *chart) Find() (cc types.ChartSet, err error) { - if cc, err = svc.chartRepo.Find(); err != nil { - return nil, err - } else { - return cc.Filter(func(m *types.Chart) (bool, error) { - return svc.prmSvc.CanReadChart(m), nil - }) +func (svc *chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { + set, f, err = svc.chartRepo.Find(filter) + if err != nil { + return } + + set, _ = set.Filter(func(m *types.Chart) (bool, error) { + return svc.prmSvc.CanReadChart(m), nil + }) + + return } func (svc *chart) Create(mod *types.Chart) (c *types.Chart, err error) { if !svc.prmSvc.CanCreateChart(crmNamespace()) { - return nil, errors.New("not allowed to create this chart") + return nil, ErrNoCreatePermissions.withStack() } - return c, svc.db.Transaction(func() error { - c, err = svc.chartRepo.Create(mod) - return err - }) + return svc.chartRepo.Create(mod) } func (svc *chart) Update(mod *types.Chart) (c *types.Chart, err error) { - validate := func() error { - if mod.ID == 0 { - return errors.New("Error updating chart: invalid ID") - } else if c, err = svc.chartRepo.FindByID(mod.ID); err != nil { - return errors.Wrap(err, "Error while loading chart for update") - } else { - if !svc.prmSvc.CanUpdateChart(c) { - return errors.New("not allowed to update this chart") - } - - mod.CreatedAt = c.CreatedAt - } - - return nil + if mod.ID == 0 { + return nil, ErrInvalidID.withStack() } - if err = validate(); err != nil { - return nil, err + if c, err = svc.chartRepo.FindByID(mod.NamespaceID, mod.ID); err != nil { + return + } + + if isStale(mod.UpdatedAt, c.UpdatedAt, c.CreatedAt) { + return nil, ErrStaleData.withStack() + } + + if !svc.prmSvc.CanUpdateChart(c) { + return nil, ErrNoUpdatePermissions.withStack() } c.Config = mod.Config c.Name = mod.Name - return c, svc.db.Transaction(func() error { - c, err = svc.chartRepo.Update(c) - return err - }) + return svc.chartRepo.Update(c) } -func (svc *chart) DeleteByID(ID uint64) error { - if c, err := svc.chartRepo.FindByID(ID); err != nil { - return errors.Wrap(err, "could not delete chart") - } else if !svc.prmSvc.CanDeleteChart(c) { - return errors.New("not allowed to delete this chart") +func (svc *chart) DeleteByID(namespaceID, chartID uint64) error { + if namespaceID == 0 { + return ErrNamespaceRequired.withStack() } - return svc.chartRepo.DeleteByID(ID) + if c, err := svc.chartRepo.FindByID(namespaceID, chartID); err != nil { + return err + } else if !svc.prmSvc.CanDeleteChart(c) { + return ErrNoDeletePermissions.withStack() + } + + return svc.chartRepo.DeleteByID(namespaceID, chartID) } diff --git a/compose/rest/chart.go b/compose/rest/chart.go index e38f84016..0dde7f717 100644 --- a/compose/rest/chart.go +++ b/compose/rest/chart.go @@ -14,45 +14,109 @@ import ( var _ = errors.Wrap -type Chart struct { - module service.ModuleService - chart service.ChartService -} +type ( + chartPayload struct { + *types.Chart + + CanUpdateChart bool `json:"canUpdateChart"` + CanDeleteChart bool `json:"canDeleteChart"` + } + + chartSetPayload struct { + Filter types.ChartFilter `json:"filter"` + Set []*chartPayload `json:"set"` + } + + Chart struct { + chart service.ChartService + permissions service.PermissionsService + } +) func (Chart) New() *Chart { return &Chart{ - module: service.DefaultModule, - chart: service.DefaultChart, + chart: service.DefaultChart, + permissions: service.DefaultPermissions, } } -func (ctrl *Chart) List(ctx context.Context, r *request.ChartList) (interface{}, error) { - return ctrl.chart.With(ctx).Find() -} - -func (ctrl *Chart) Create(ctx context.Context, r *request.ChartCreate) (interface{}, error) { - chart := &types.Chart{ - Name: r.Name, - Config: r.Config, +func (ctrl Chart) List(ctx context.Context, r *request.ChartList) (interface{}, error) { + f := types.ChartFilter{ + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, } - return ctrl.chart.With(ctx).Create(chart) + set, filter, err := ctrl.chart.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, set, filter, err) } -func (ctrl *Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, error) { - return ctrl.chart.With(ctx).FindByID(r.ChartID) -} - -func (ctrl *Chart) Update(ctx context.Context, r *request.ChartUpdate) (interface{}, error) { - chart := &types.Chart{ - ID: r.ChartID, - Name: r.Name, - Config: r.Config, +func (ctrl Chart) Create(ctx context.Context, r *request.ChartCreate) (interface{}, error) { + var err error + ns := &types.Chart{ + NamespaceID: r.NamespaceID, + Name: r.Name, + Config: r.Config, } - return ctrl.chart.With(ctx).Update(chart) + ns, err = ctrl.chart.With(ctx).Create(ns) + return ctrl.makePayload(ctx, ns, err) } -func (ctrl *Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface{}, error) { - return resputil.OK(), ctrl.chart.With(ctx).DeleteByID(r.ChartID) +func (ctrl Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, error) { + return ctrl.chart.With(ctx).FindByID(r.NamespaceID, r.ChartID) +} + +func (ctrl Chart) Update(ctx context.Context, r *request.ChartUpdate) (interface{}, error) { + var ( + ns = &types.Chart{} + err error + ) + + ns.ID = r.ChartID + ns.Name = r.Name + ns.Config = r.Config + ns.NamespaceID = r.NamespaceID + ns.UpdatedAt = r.UpdatedAt + + ns, err = ctrl.chart.With(ctx).Update(ns) + return ctrl.makePayload(ctx, ns, err) +} + +func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface{}, error) { + _, err := ctrl.chart.With(ctx).FindByID(r.NamespaceID, r.ChartID) + if err != nil { + return nil, err + } + + return resputil.OK(), ctrl.chart.With(ctx).DeleteByID(r.NamespaceID, r.ChartID) +} + +func (ctrl Chart) makePayload(ctx context.Context, ns *types.Chart, err error) (*chartPayload, error) { + if err != nil || ns == nil { + return nil, err + } + + perm := ctrl.permissions.With(ctx) + + return &chartPayload{ + Chart: ns, + + CanUpdateChart: perm.CanUpdateChart(ns), + CanDeleteChart: perm.CanDeleteChart(ns), + }, nil +} + +func (ctrl Chart) makeFilterPayload(ctx context.Context, nn types.ChartSet, f types.ChartFilter, err error) (*chartSetPayload, error) { + if err != nil { + return nil, err + } + + nsp := &chartSetPayload{Filter: f, Set: make([]*chartPayload, len(nn))} + + for i := range nn { + nsp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + } + + return nsp, nil } diff --git a/compose/rest/request/chart.go b/compose/rest/request/chart.go index b90d61713..6002c4dcf 100644 --- a/compose/rest/request/chart.go +++ b/compose/rest/request/chart.go @@ -27,6 +27,7 @@ import ( "github.com/pkg/errors" sqlxTypes "github.com/jmoiron/sqlx/types" + "time" ) var _ = chi.URLParam @@ -34,6 +35,9 @@ var _ = multipart.FileHeader{} // Chart list request parameters type ChartList struct { + Query string + Page uint + PerPage uint NamespaceID uint64 `json:",string"` } @@ -68,6 +72,18 @@ func (cReq *ChartList) Fill(r *http.Request) (err error) { post[name] = string(param[0]) } + if val, ok := get["query"]; ok { + + cReq.Query = val + } + if val, ok := get["page"]; ok { + + cReq.Page = parseUint(val) + } + if val, ok := get["perPage"]; ok { + + cReq.PerPage = parseUint(val) + } cReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err @@ -181,6 +197,7 @@ type ChartUpdate struct { NamespaceID uint64 `json:",string"` Config sqlxTypes.JSONText Name string + UpdatedAt *time.Time } func NewChartUpdate() *ChartUpdate { @@ -226,6 +243,12 @@ func (cReq *ChartUpdate) Fill(r *http.Request) (err error) { cReq.Name = val } + if val, ok := post["updatedAt"]; ok { + + if cReq.UpdatedAt, err = parseISODatePtrWithErr(val); err != nil { + return err + } + } return err } diff --git a/compose/types/chart.go b/compose/types/chart.go index 0832a8e43..3930aaaf9 100644 --- a/compose/types/chart.go +++ b/compose/types/chart.go @@ -22,11 +22,12 @@ type ( } ChartFilter struct { - Query string `json:"query"` - Page uint `json:"page"` - PerPage uint `json:"perPage"` - Sort string `json:"sort"` - Count uint `json:"count"` + NamespaceID uint64 `json:"namespaceID,string"` + Query string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + // Sort string `json:"sort"` + Count uint `json:"count"` } ) diff --git a/docs/compose/README.md b/docs/compose/README.md index 5e81254c1..f833825a7 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -115,13 +115,13 @@ | Method | Endpoint | Purpose | | ------ | -------- | ------- | -| `GET` | `/namespace/{namespaceID}/chart/` | List/read charts from module section | -| `POST` | `/namespace/{namespaceID}/chart/` | List/read charts from module section | -| `GET` | `/namespace/{namespaceID}/chart/{chartID}` | Read charts by ID from module section | -| `POST` | `/namespace/{namespaceID}/chart/{chartID}` | Add/update charts in module section | +| `GET` | `/namespace/{namespaceID}/chart/` | List/read charts | +| `POST` | `/namespace/{namespaceID}/chart/` | List/read charts | +| `GET` | `/namespace/{namespaceID}/chart/{chartID}` | Read charts by ID | +| `POST` | `/namespace/{namespaceID}/chart/{chartID}` | Add/update charts | | `DELETE` | `/namespace/{namespaceID}/chart/{chartID}` | Delete chart | -## List/read charts from module section +## List/read charts #### Method @@ -133,9 +133,12 @@ | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | +| query | string | GET | Search query to match against charts | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | -## List/read charts from module section +## List/read charts #### Method @@ -151,7 +154,7 @@ | name | string | POST | Chart name | N/A | YES | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | -## Read charts by ID from module section +## Read charts by ID #### Method @@ -166,7 +169,7 @@ | chartID | uint64 | PATH | Chart ID | N/A | YES | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | -## Add/update charts in module section +## Add/update charts #### Method @@ -182,6 +185,7 @@ | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | config | sqlxTypes.JSONText | POST | Chart JSON | N/A | YES | | name | string | POST | Chart name | N/A | YES | +| updatedAt | *time.Time | POST | Last update (or creation) date | N/A | NO | ## Delete chart @@ -374,7 +378,7 @@ Compose module definitions | slug | string | POST | Slug (url path part) | N/A | YES | | enabled | bool | POST | Enabled | N/A | YES | | meta | sqlxTypes.JSONText | POST | Meta data | N/A | YES | -| updatedAt | *time.Time | POST | Last update (or creation) date | N/A | YES | +| updatedAt | *time.Time | POST | Last update (or creation) date | N/A | NO | ## Delete namespace From 40999e623e863d67d25c9a331e149c7130da3a82 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sun, 28 Apr 2019 14:16:40 +0200 Subject: [PATCH 06/14] Apply namespace changes to all layers of modules --- api/compose/spec.json | 23 +++- api/compose/spec/module.json | 21 +++- api/compose/spec/page.json | 2 +- compose/internal/repository/module.go | 141 ++++++++++++++----------- compose/internal/service/attachment.go | 4 +- compose/internal/service/module.go | 122 ++++++++++++--------- compose/internal/service/record.go | 10 +- compose/rest/module.go | 101 +++++++++++++++--- compose/rest/request/module.go | 18 ++++ compose/types/module.go | 9 ++ docs/compose/README.md | 5 +- 11 files changed, 315 insertions(+), 141 deletions(-) diff --git a/api/compose/spec.json b/api/compose/spec.json index 66af9eb76..add300e05 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -185,7 +185,7 @@ }, { "title": "Pages", - "description": "Compose module pages", + "description": "Compose pages", "entrypoint": "page", "path": "/namespace/{namespaceID}/page", "authentication": [], @@ -432,7 +432,8 @@ { "imports": [ "sqlxTypes github.com/jmoiron/sqlx/types", - "github.com/crusttech/crust/compose/types" + "github.com/crusttech/crust/compose/types", + "time" ] } ], @@ -449,6 +450,18 @@ "name": "query", "required": false, "title": "Search query" + }, + { + "name": "page", + "type": "uint", + "required": false, + "title": "Page number (0 based)" + }, + { + "name": "perPage", + "type": "uint", + "required": false, + "title": "Returned items per page (default 50)" } ] } @@ -529,6 +542,12 @@ "name": "meta", "required": true, "title": "Module meta data" + }, + { + "type": "*time.Time", + "name": "updatedAt", + "required": false, + "title": "Last update (or creation) date" } ] } diff --git a/api/compose/spec/module.json b/api/compose/spec/module.json index 1ca51e4d8..a123fa355 100644 --- a/api/compose/spec/module.json +++ b/api/compose/spec/module.json @@ -6,7 +6,8 @@ { "imports": [ "sqlxTypes github.com/jmoiron/sqlx/types", - "github.com/crusttech/crust/compose/types" + "github.com/crusttech/crust/compose/types", + "time" ] } ], @@ -36,6 +37,18 @@ "required": false, "title": "Search query", "type": "string" + }, + { + "name": "page", + "required": false, + "title": "Page number (0 based)", + "type": "uint" + }, + { + "name": "perPage", + "required": false, + "title": "Returned items per page (default 50)", + "type": "uint" } ] } @@ -116,6 +129,12 @@ "required": true, "title": "Module meta data", "type": "sqlxTypes.JSONText" + }, + { + "name": "updatedAt", + "required": false, + "title": "Last update (or creation) date", + "type": "*time.Time" } ] } diff --git a/api/compose/spec/page.json b/api/compose/spec/page.json index 19f9bb502..9f70840c7 100644 --- a/api/compose/spec/page.json +++ b/api/compose/spec/page.json @@ -1,6 +1,6 @@ { "Title": "Pages", - "Description": "Compose module pages", + "Description": "Compose pages", "Interface": "Page", "Struct": [ { diff --git a/compose/internal/repository/module.go b/compose/internal/repository/module.go index f7a9299de..1d377e126 100644 --- a/compose/internal/repository/module.go +++ b/compose/internal/repository/module.go @@ -7,6 +7,7 @@ import ( "github.com/jmoiron/sqlx" "github.com/pkg/errors" "github.com/titpetric/factory" + "gopkg.in/Masterminds/squirrel.v1" "github.com/crusttech/crust/compose/types" ) @@ -15,11 +16,12 @@ type ( ModuleRepository interface { With(ctx context.Context, db *factory.DB) ModuleRepository - FindByID(id uint64) (*types.Module, error) - Find() (types.ModuleSet, error) + FindByID(namespaceID, moduleID uint64) (*types.Module, error) + Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) + FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err error) Create(mod *types.Module) (*types.Module, error) Update(mod *types.Module) (*types.Module, error) - DeleteByID(id uint64) error + DeleteByID(namespaceID, moduleID uint64) error } module struct { @@ -28,60 +30,81 @@ type ( ) const ( - sqlModuleColumns = ` - id, name, json, - created_at, updated_at, deleted_at - ` - sqlModuleSelect = ` - SELECT ` + sqlModuleColumns + ` FROM compose_module WHERE deleted_at IS NULL - ` + ErrModuleNotFound = repositoryError("ModuleNotFound") ) func Module(ctx context.Context, db *factory.DB) ModuleRepository { return (&module{}).With(ctx, db) } -func (r *module) With(ctx context.Context, db *factory.DB) ModuleRepository { +func (r module) With(ctx context.Context, db *factory.DB) ModuleRepository { return &module{ repository: r.repository.With(ctx, db), } } -// @todo: update to accepted DeletedAt column semantics from Messaging - -func (r *module) FindByID(id uint64) (mod *types.Module, err error) { - mod = &types.Module{} - - if err = r.db().Get(mod, sqlModuleSelect+" AND id = ? ", id); err != nil { - return - } - - if mod.Fields, err = r.fields(id); err != nil { - return - } - - return +func (r module) table() string { + return "compose_module" } -func (r *module) Find() (mm types.ModuleSet, err error) { - if err = r.db().Select(&mm, sqlModuleSelect+" ORDER BY id ASC"); err != nil { - return +func (r module) columns() []string { + return []string{ + "id", "rel_namespace", "name", "json", + "created_at", "updated_at", "deleted_at", } - - var ff types.ModuleFieldSet - if ff, err = r.fields(mm.IDs()...); err != nil { - return - } else { - _ = ff.Walk(func(f *types.ModuleField) error { - mm.FindByID(f.ModuleID).Fields = append(mm.FindByID(f.ModuleID).Fields, f) - return nil - }) - } - - return mm, nil } -func (r *module) Create(mod *types.Module) (*types.Module, error) { +func (r module) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table()). + Where("deleted_at IS NULL") + +} + +func (r module) FindByID(namespaceID, moduleID uint64) (*types.Module, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("id = ?", moduleID) + + c = &types.Module{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) + } + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrModuleNotFound) +} + +func (r module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) { + f = filter + f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50) + + query := r.query() + + if filter.NamespaceID > 0 { + query = query.Where("a.rel_namespace = ?", filter.NamespaceID) + } + + if f.Query != "" { + q := "%" + f.Query + "%" + query = query.Where("name like ?", q) + } + + if f.Count, err = r.count(query); err != nil || f.Count == 0 { + return + } + + query = query. + Columns(r.columns()...). + OrderBy("id ASC") + + return set, f, r.fetchPaged(&set, query, f.Page, f.PerPage) +} + +func (r module) Create(mod *types.Module) (*types.Module, error) { mod.ID = factory.Sonyflake.NextID() mod.CreatedAt = time.Now() @@ -89,10 +112,10 @@ func (r *module) Create(mod *types.Module) (*types.Module, error) { return nil, err } - return mod, r.db().Insert("compose_module", mod) + return mod, r.db().Insert(r.table(), mod) } -func (r *module) Update(mod *types.Module) (*types.Module, error) { +func (r module) Update(mod *types.Module) (*types.Module, error) { now := time.Now() mod.UpdatedAt = &now @@ -100,10 +123,10 @@ func (r *module) Update(mod *types.Module) (*types.Module, error) { return nil, err } - return mod, r.db().Replace("compose_module", mod) + return mod, r.db().Replace(r.table(), mod) } -func (r *module) updateFields(moduleID uint64, ff types.ModuleFieldSet) error { +func (r module) updateFields(moduleID uint64, ff types.ModuleFieldSet) error { // @todo be more selective when deleting if _, err := r.db().Exec("DELETE FROM compose_module_form WHERE module_id = ?", moduleID); err != nil { return errors.Wrap(err, "Error updating module fields") @@ -120,32 +143,24 @@ func (r *module) updateFields(moduleID uint64, ff types.ModuleFieldSet) error { return nil } -func (r *module) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM compose_module WHERE id=?", id) +func (r module) DeleteByID(namespaceID, attachmentID uint64) error { + _, err := r.db().Exec( + "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", + namespaceID, + attachmentID, + ) + return err } -func (r *module) fields(IDs ...uint64) (ff types.ModuleFieldSet, err error) { - if len(IDs) == 0 { +func (r module) FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err error) { + if len(moduleIDs) == 0 { return } - if sql, args, err := sqlx.In("SELECT * FROM compose_module_form WHERE module_id IN (?) ORDER BY module_id AND place", IDs); err != nil { + if sql, args, err := sqlx.In("SELECT * FROM compose_module_form WHERE module_id IN (?) ORDER BY module_id AND place", moduleIDs); err != nil { return nil, err } else { return ff, r.db().Select(&ff, sql, args...) } } - -// // FieldNames returns a slice of field names, used for ordering record row columns -// func (r *module) FieldNames(mod *types.Module) ([]string, error) { -// if fields, err := r.Fields(mod.ID); err != nil { -// return []string{}, err -// } else { -// result := make([]string, len(fields)) -// for k, v := range fields { -// result[k] = v.Name -// } -// return result, nil -// } -// } diff --git a/compose/internal/service/attachment.go b/compose/internal/service/attachment.go index c19057b8d..0d522fb9f 100644 --- a/compose/internal/service/attachment.go +++ b/compose/internal/service/attachment.go @@ -114,7 +114,7 @@ func (svc attachment) Find(filter types.AttachmentFilter) (types.AttachmentSet, } if filter.ModuleID > 0 { - if _, err := svc.moduleSvc.FindByID(filter.ModuleID); err != nil { + if _, err := svc.moduleSvc.FindByID(filter.NamespaceID, filter.ModuleID); err != nil { return nil, filter, err } } @@ -174,7 +174,7 @@ func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, si var currentUserID uint64 = auth.GetIdentityFromContext(svc.ctx).Identity() - if _, err := svc.moduleSvc.FindByID(moduleID); err != nil { + if _, err := svc.moduleSvc.FindByID(namespaceID, moduleID); err != nil { return nil, err } else if r, err := svc.recordSvc.FindByID(recordID); err != nil { return nil, err diff --git a/compose/internal/service/module.go b/compose/internal/service/module.go index f687335fd..ed723ca76 100644 --- a/compose/internal/service/module.go +++ b/compose/internal/service/module.go @@ -3,7 +3,6 @@ package service import ( "context" - "github.com/pkg/errors" "github.com/titpetric/factory" "github.com/crusttech/crust/compose/internal/repository" @@ -24,12 +23,12 @@ type ( ModuleService interface { With(ctx context.Context) ModuleService - FindByID(moduleID uint64) (*types.Module, error) - Find() (types.ModuleSet, error) + FindByID(namespaceID, moduleID uint64) (*types.Module, error) + Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) Create(module *types.Module) (*types.Module, error) Update(module *types.Module) (*types.Module, error) - DeleteByID(moduleID uint64) error + DeleteByID(namespaceID, moduleID uint64) error } ) @@ -52,75 +51,96 @@ func (svc *module) With(ctx context.Context) ModuleService { } } -func (svc *module) FindByID(id uint64) (m *types.Module, err error) { - if m, err = svc.moduleRepo.FindByID(id); err != nil { +func (svc *module) FindByID(namespaceID, moduleID uint64) (m *types.Module, err error) { + if namespaceID == 0 { + return nil, ErrNamespaceRequired + } + + if m, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { return } else if !svc.prmSvc.CanReadModule(m) { - return nil, errors.New("not allowed to access this module") + return nil, ErrNoReadPermissions.withStack() + } + + var ff types.ModuleFieldSet + if ff, err = svc.moduleRepo.FindFields(m.ID); err != nil { + return + } else { + _ = ff.Walk(func(f *types.ModuleField) error { + m.Fields = append(m.Fields, f) + return nil + }) } return } -func (svc *module) Find() (mm types.ModuleSet, err error) { - if mm, err = svc.moduleRepo.Find(); err != nil { - return nil, err +func (svc *module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) { + set, f, err = svc.moduleRepo.Find(filter) + if err != nil { + return + } + + set, _ = set.Filter(func(m *types.Module) (bool, error) { + return svc.prmSvc.CanReadModule(m), nil + }) + + // Preload all fields and update all modules + var ff types.ModuleFieldSet + if ff, err = svc.moduleRepo.FindFields(set.IDs()...); err != nil { + return } else { - return mm.Filter(func(m *types.Module) (bool, error) { - return svc.prmSvc.CanReadModule(m), nil + _ = ff.Walk(func(f *types.ModuleField) error { + set.FindByID(f.ModuleID).Fields = append(set.FindByID(f.ModuleID).Fields, f) + return nil }) } + + return } func (svc *module) Create(mod *types.Module) (*types.Module, error) { if !svc.prmSvc.CanCreateModule(crmNamespace()) { - return nil, errors.New("not allowed to create this module") - } - - if len(mod.Fields) == 0 { - return nil, errors.New("Error creating module: no fields") + return nil, ErrNoCreatePermissions.withStack() } return svc.moduleRepo.Create(mod) } -func (svc *module) Update(module *types.Module) (m *types.Module, err error) { - validate := func() error { - if module.ID == 0 { - return errors.New("Error updating module: invalid ID") - } else if m, err = svc.moduleRepo.FindByID(module.ID); err != nil { - return errors.Wrap(err, "Error while loading module for update") - } else { - if !svc.prmSvc.CanUpdateModule(m) { - return errors.New("not allowed to update this module") - } - - module.CreatedAt = m.CreatedAt - } - - if len(module.Fields) == 0 { - return errors.New("Error updating module: no fields") - } - - return nil +func (svc *module) Update(mod *types.Module) (m *types.Module, err error) { + if mod.ID == 0 { + return nil, ErrInvalidID.withStack() } - if err = validate(); err != nil { - return nil, err - } - - return m, svc.db.Transaction(func() (err error) { - m, err = svc.moduleRepo.Update(module) + if m, err = svc.moduleRepo.FindByID(mod.NamespaceID, mod.ID); err != nil { return - }) -} - -func (svc *module) DeleteByID(ID uint64) error { - if m, err := svc.moduleRepo.FindByID(ID); err != nil { - return errors.Wrap(err, "could not delete module") - } else if !svc.prmSvc.CanDeleteModule(m) { - return errors.New("not allowed to delete this module") } - return svc.moduleRepo.DeleteByID(ID) + if isStale(mod.UpdatedAt, m.UpdatedAt, m.CreatedAt) { + return nil, ErrStaleData.withStack() + } + + if !svc.prmSvc.CanUpdateModule(m) { + return nil, ErrNoUpdatePermissions.withStack() + } + + m.Name = mod.Name + m.Meta = mod.Meta + m.Fields = mod.Fields + + return svc.moduleRepo.Update(m) +} + +func (svc *module) DeleteByID(namespaceID, moduleID uint64) error { + if namespaceID == 0 { + return ErrNamespaceRequired.withStack() + } + + if c, err := svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { + return err + } else if !svc.prmSvc.CanDeleteModule(c) { + return ErrNoDeletePermissions.withStack() + } + + return svc.moduleRepo.DeleteByID(namespaceID, moduleID) } diff --git a/compose/internal/service/record.go b/compose/internal/service/record.go index 1b9a44445..6e70bb8a9 100644 --- a/compose/internal/service/record.go +++ b/compose/internal/service/record.go @@ -85,9 +85,10 @@ func (svc *record) FindByID(recordID uint64) (r *types.Record, err error) { func (svc *record) Report(moduleID uint64, metrics, dimensions, filter string) (out interface{}, err error) { var module *types.Module + var namespaceID uint64 = 0 err = svc.db.Transaction(func() (err error) { - if module, err = svc.moduleRepo.FindByID(moduleID); err != nil { + if module, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { return } else if !svc.prmSvc.CanReadRecord(module) { return errors.New("not allowed to access this record") @@ -102,9 +103,10 @@ func (svc *record) Report(moduleID uint64, metrics, dimensions, filter string) ( func (svc *record) Find(moduleID uint64, filter, sort string, page, perPage int) (rsp *repository.FindResponse, err error) { var module *types.Module + var namespaceID uint64 = 0 err = svc.db.Transaction(func() (err error) { - if module, err = svc.moduleRepo.FindByID(moduleID); err != nil { + if module, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { return } else if !svc.prmSvc.CanReadRecord(module) { return errors.New("not allowed to access this record") @@ -129,7 +131,7 @@ func (svc *record) Create(in *types.Record) (record *types.Record, err error) { var module *types.Module err = svc.db.Transaction(func() (err error) { - if module, err = svc.moduleRepo.FindByID(in.ModuleID); err != nil { + if module, err = svc.moduleRepo.FindByID(in.NamespaceID, in.ModuleID); err != nil { return } else if !svc.prmSvc.CanCreateRecord(module) { return errors.New("not allowed to create records for this module") @@ -174,7 +176,7 @@ func (svc *record) Update(updated *types.Record) (record *types.Record, err erro return errors.New("not allowed to update this record") } - if module, err = svc.moduleRepo.FindByID(updated.ModuleID); err != nil { + if module, err = svc.moduleRepo.FindByID(updated.NamespaceID, updated.ModuleID); err != nil { return } diff --git a/compose/rest/module.go b/compose/rest/module.go index 63d966058..450b0fec5 100644 --- a/compose/rest/module.go +++ b/compose/rest/module.go @@ -11,29 +11,50 @@ import ( ) type ( + modulePayload struct { + *types.Module + + CanUpdateModule bool `json:"canUpdateModule"` + CanDeleteModule bool `json:"canDeleteModule"` + CanCreateRecord bool `json:"canCreateRecord"` + CanReadRecord bool `json:"canReadRecord"` + CanUpdateRecord bool `json:"canUpdateRecord"` + CanDeleteRecord bool `json:"canDeleteRecord"` + } + + moduleSetPayload struct { + Filter types.ModuleFilter `json:"filter"` + Set []*modulePayload `json:"set"` + } + Module struct { - module service.ModuleService - record service.RecordService + module service.ModuleService + record service.RecordService + permissions service.PermissionsService } ) func (Module) New() *Module { return &Module{ - module: service.DefaultModule, - record: service.DefaultRecord, + module: service.DefaultModule, + record: service.DefaultRecord, + permissions: service.DefaultPermissions, } } func (ctrl *Module) List(ctx context.Context, r *request.ModuleList) (interface{}, error) { - return ctrl.module.With(ctx).Find() + f := types.ModuleFilter{ + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, + } + + set, filter, err := ctrl.module.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, set, filter, err) } func (ctrl *Module) Read(ctx context.Context, r *request.ModuleRead) (interface{}, error) { - return ctrl.module.With(ctx).FindByID(r.ModuleID) -} - -func (ctrl *Module) Delete(ctx context.Context, r *request.ModuleDelete) (interface{}, error) { - return resputil.OK(), ctrl.module.With(ctx).DeleteByID(r.ModuleID) + return ctrl.module.With(ctx).FindByID(r.NamespaceID, r.ModuleID) } func (ctrl *Module) Create(ctx context.Context, r *request.ModuleCreate) (interface{}, error) { @@ -46,11 +67,59 @@ func (ctrl *Module) Create(ctx context.Context, r *request.ModuleCreate) (interf } func (ctrl *Module) Update(ctx context.Context, r *request.ModuleUpdate) (interface{}, error) { - item := &types.Module{ - ID: r.ModuleID, - Name: r.Name, - Fields: r.Fields, - Meta: r.Meta, + var ( + m = &types.Module{} + err error + ) + + m.ID = r.ModuleID + m.Name = r.Name + m.Meta = r.Meta + m.NamespaceID = r.NamespaceID + m.UpdatedAt = r.UpdatedAt + + m, err = ctrl.module.With(ctx).Update(m) + return ctrl.makePayload(ctx, m, err) +} + +func (ctrl *Module) Delete(ctx context.Context, r *request.ModuleDelete) (interface{}, error) { + _, err := ctrl.module.With(ctx).FindByID(r.NamespaceID, r.ModuleID) + if err != nil { + return nil, err } - return ctrl.module.With(ctx).Update(item) + + return resputil.OK(), ctrl.module.With(ctx).DeleteByID(r.NamespaceID, r.ModuleID) +} + +func (ctrl Module) makePayload(ctx context.Context, m *types.Module, err error) (*modulePayload, error) { + if err != nil || m == nil { + return nil, err + } + + perm := ctrl.permissions.With(ctx) + + return &modulePayload{ + Module: m, + + CanUpdateModule: perm.CanUpdateModule(m), + CanDeleteModule: perm.CanDeleteModule(m), + CanCreateRecord: perm.CanCreateRecord(m), + CanReadRecord: perm.CanReadRecord(m), + CanUpdateRecord: perm.CanUpdateRecord(m), + CanDeleteRecord: perm.CanDeleteRecord(m), + }, nil +} + +func (ctrl Module) makeFilterPayload(ctx context.Context, nn types.ModuleSet, f types.ModuleFilter, err error) (*moduleSetPayload, error) { + if err != nil { + return nil, err + } + + msp := &moduleSetPayload{Filter: f, Set: make([]*modulePayload, len(nn))} + + for i := range nn { + msp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + } + + return msp, nil } diff --git a/compose/rest/request/module.go b/compose/rest/request/module.go index 4d70c539d..fe51fa52b 100644 --- a/compose/rest/request/module.go +++ b/compose/rest/request/module.go @@ -28,6 +28,7 @@ import ( "github.com/crusttech/crust/compose/types" sqlxTypes "github.com/jmoiron/sqlx/types" + "time" ) var _ = chi.URLParam @@ -36,6 +37,8 @@ var _ = multipart.FileHeader{} // Module list request parameters type ModuleList struct { Query string + Page uint + PerPage uint NamespaceID uint64 `json:",string"` } @@ -74,6 +77,14 @@ func (mReq *ModuleList) Fill(r *http.Request) (err error) { mReq.Query = val } + if val, ok := get["page"]; ok { + + mReq.Page = parseUint(val) + } + if val, ok := get["perPage"]; ok { + + mReq.PerPage = parseUint(val) + } mReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err @@ -189,6 +200,7 @@ type ModuleUpdate struct { Name string Fields types.ModuleFieldSet Meta sqlxTypes.JSONText + UpdatedAt *time.Time } func NewModuleUpdate() *ModuleUpdate { @@ -234,6 +246,12 @@ func (mReq *ModuleUpdate) Fill(r *http.Request) (err error) { return err } } + if val, ok := post["updatedAt"]; ok { + + if mReq.UpdatedAt, err = parseISODatePtrWithErr(val); err != nil { + return err + } + } return err } diff --git a/compose/types/module.go b/compose/types/module.go index 06590f390..a6f119595 100644 --- a/compose/types/module.go +++ b/compose/types/module.go @@ -22,6 +22,15 @@ type ( UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"` DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` } + + ModuleFilter struct { + NamespaceID uint64 `json:"namespaceID,string"` + Query string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + // Sort string `json:"sort"` + Count uint `json:"count"` + } ) // Resource returns a system resource ID for this type diff --git a/docs/compose/README.md b/docs/compose/README.md index f833825a7..f9e06862e 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -232,6 +232,8 @@ Compose module definitions | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | query | string | GET | Search query | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Create module @@ -283,6 +285,7 @@ Compose module definitions | name | string | POST | Module Name | N/A | YES | | fields | types.ModuleFieldSet | POST | Fields JSON | N/A | YES | | meta | sqlxTypes.JSONText | POST | Module meta data | N/A | YES | +| updatedAt | *time.Time | POST | Last update (or creation) date | N/A | NO | ## Delete module @@ -432,7 +435,7 @@ Compose Notifications # Pages -Compose module pages +Compose pages | Method | Endpoint | Purpose | | ------ | -------- | ------- | From 0c2286be835a275269f65bf0c1f3c394d211669b Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sun, 28 Apr 2019 14:33:01 +0200 Subject: [PATCH 07/14] Apply namespace changes to all layers of triggers --- api/compose/spec.json | 31 ++++++ api/compose/spec/trigger.json | 32 ++++++- compose/internal/repository/chart.go | 1 - compose/internal/repository/trigger.go | 104 +++++++++++++------- compose/internal/service/chart.go | 12 +-- compose/internal/service/trigger.go | 115 ++++++++++++----------- compose/rest/chart.go | 10 +- compose/rest/request/trigger.go | 24 +++++ compose/rest/trigger.go | 125 +++++++++++++++++-------- compose/types/trigger.go | 6 ++ docs/compose/README.md | 4 + 11 files changed, 322 insertions(+), 142 deletions(-) diff --git a/api/compose/spec.json b/api/compose/spec.json index add300e05..c34e4037d 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -926,6 +926,13 @@ } ] }, + "struct": [ + { + "imports": [ + "time" + ] + } + ], "apis": [ { "name": "list", @@ -939,6 +946,24 @@ "name": "moduleID", "required": false, "title": "Filter triggers by module" + }, + { + "type": "string", + "name": "query", + "required": false, + "title": "Search query" + }, + { + "name": "page", + "type": "uint", + "required": false, + "title": "Page number (0 based)" + }, + { + "name": "perPage", + "type": "uint", + "required": false, + "title": "Returned items per page (default 50)" } ] } @@ -979,6 +1004,12 @@ "name": "source", "required": false, "title": "Trigger source code" + }, + { + "type": "*time.Time", + "name": "updatedAt", + "required": false, + "title": "Last update (or creation) date" } ] } diff --git a/api/compose/spec/trigger.json b/api/compose/spec/trigger.json index fafe5c3a4..dad412d7a 100644 --- a/api/compose/spec/trigger.json +++ b/api/compose/spec/trigger.json @@ -2,7 +2,13 @@ "Title": "Triggers", "Description": "Compose Triggers", "Interface": "Trigger", - "Struct": null, + "Struct": [ + { + "imports": [ + "time" + ] + } + ], "Parameters": { "path": [ { @@ -29,6 +35,24 @@ "required": false, "title": "Filter triggers by module", "type": "uint64" + }, + { + "name": "query", + "required": false, + "title": "Search query", + "type": "string" + }, + { + "name": "page", + "required": false, + "title": "Page number (0 based)", + "type": "uint" + }, + { + "name": "perPage", + "required": false, + "title": "Returned items per page (default 50)", + "type": "uint" } ] } @@ -69,6 +93,12 @@ "required": false, "title": "Trigger source code", "type": "string" + }, + { + "name": "updatedAt", + "required": false, + "title": "Last update (or creation) date", + "type": "*time.Time" } ] } diff --git a/compose/internal/repository/chart.go b/compose/internal/repository/chart.go index 70d432dce..00dfcf63a 100644 --- a/compose/internal/repository/chart.go +++ b/compose/internal/repository/chart.go @@ -56,7 +56,6 @@ func (r chart) query() squirrel.SelectBuilder { Select(). From(r.table()). Where("deleted_at IS NULL") - } func (r chart) FindByID(namespaceID, chartID uint64) (*types.Chart, error) { diff --git a/compose/internal/repository/trigger.go b/compose/internal/repository/trigger.go index 27a1d4525..cf3588776 100644 --- a/compose/internal/repository/trigger.go +++ b/compose/internal/repository/trigger.go @@ -14,11 +14,11 @@ type ( TriggerRepository interface { With(ctx context.Context, db *factory.DB) TriggerRepository - FindByID(id uint64) (*types.Trigger, error) - Find(filter types.TriggerFilter) (types.TriggerSet, error) + FindByID(namespaceID, attachmentID uint64) (*types.Trigger, error) + Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) Create(mod *types.Trigger) (*types.Trigger, error) Update(mod *types.Trigger) (*types.Trigger, error) - DeleteByID(id uint64) error + DeleteByID(namespaceID, attachmentID uint64) error } trigger struct { @@ -26,66 +26,100 @@ type ( } ) +const ( + ErrTriggerNotFound = repositoryError("TriggerNotFound") +) + func Trigger(ctx context.Context, db *factory.DB) TriggerRepository { return (&trigger{}).With(ctx, db) } -func (r *trigger) With(ctx context.Context, db *factory.DB) TriggerRepository { +func (r trigger) With(ctx context.Context, db *factory.DB) TriggerRepository { return &trigger{ repository: r.repository.With(ctx, db), } } -func (r *trigger) FindByID(id uint64) (*types.Trigger, error) { - mod := &types.Trigger{} - query := r.query().Where("id = ?", id) +func (r trigger) table() string { + return "compose_trigger" +} - if sql, args, err := query.ToSql(); err != nil { - return nil, err - } else { - return mod, r.db().Get(mod, sql, args...) +func (r trigger) columns() []string { + return []string{ + "id", "rel_namespace", "name", + "actions", "enabled", "source", "rel_module", + "created_at", "updated_at", "deleted_at", } } -func (r *trigger) Find(filter types.TriggerFilter) (mod types.TriggerSet, err error) { +func (r trigger) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table()). + Where("deleted_at IS NULL") +} + +func (r trigger) FindByID(namespaceID, triggerID uint64) (*types.Trigger, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("id = ?", triggerID) + + c = &types.Trigger{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) + } + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrTriggerNotFound) +} + +func (r trigger) Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) { + f = filter + f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50) + query := r.query() - if filter.ModuleID > 0 { - query = query.Where("rel_module = ?", filter.ModuleID) + if filter.NamespaceID > 0 { + query = query.Where("a.rel_namespace = ?", filter.NamespaceID) } - if sql, args, err := query.ToSql(); err != nil { - return nil, err - } else { - return mod, r.db().Select(&mod, sql, args...) + if f.Query != "" { + q := "%" + f.Query + "%" + query = query.Where("name like ?", q) } + + if f.Count, err = r.count(query); err != nil || f.Count == 0 { + return + } + + query = query. + Columns(r.columns()...). + OrderBy("id ASC") + + return set, f, r.fetchPaged(&set, query, f.Page, f.PerPage) } -func (r trigger) query() (query squirrel.SelectBuilder) { - query = squirrel.Select(). - Columns( - "id", "name", "actions", "enabled", "source", "rel_module", - "created_at", "updated_at", "deleted_at"). - From("compose_trigger"). - OrderBy("id DESC") - - return -} - -func (r *trigger) Create(mod *types.Trigger) (*types.Trigger, error) { +func (r trigger) Create(mod *types.Trigger) (*types.Trigger, error) { mod.ID = factory.Sonyflake.NextID() mod.CreatedAt = time.Now() - return mod, r.db().Insert("compose_trigger", mod) + return mod, r.db().Insert(r.table(), mod) } -func (r *trigger) Update(mod *types.Trigger) (*types.Trigger, error) { +func (r trigger) Update(mod *types.Trigger) (*types.Trigger, error) { now := time.Now() mod.UpdatedAt = &now - return mod, r.db().Replace("compose_trigger", mod) + return mod, r.db().Replace(r.table(), mod) } -func (r *trigger) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM compose_trigger WHERE id = ?", id) +func (r trigger) DeleteByID(namespaceID, attachmentID uint64) error { + _, err := r.db().Exec( + "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", + namespaceID, + attachmentID, + ) + return err } diff --git a/compose/internal/service/chart.go b/compose/internal/service/chart.go index 60ab1c29a..9c812cbb3 100644 --- a/compose/internal/service/chart.go +++ b/compose/internal/service/chart.go @@ -37,7 +37,7 @@ func Chart() ChartService { }).With(context.Background()) } -func (svc *chart) With(ctx context.Context) ChartService { +func (svc chart) With(ctx context.Context) ChartService { db := repository.DB(ctx) return &chart{ db: db, @@ -49,7 +49,7 @@ func (svc *chart) With(ctx context.Context) ChartService { } } -func (svc *chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err error) { +func (svc chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err error) { if namespaceID == 0 { return nil, ErrNamespaceRequired } @@ -63,7 +63,7 @@ func (svc *chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err err return } -func (svc *chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { +func (svc chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { set, f, err = svc.chartRepo.Find(filter) if err != nil { return @@ -76,7 +76,7 @@ func (svc *chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.Ch return } -func (svc *chart) Create(mod *types.Chart) (c *types.Chart, err error) { +func (svc chart) Create(mod *types.Chart) (c *types.Chart, err error) { if !svc.prmSvc.CanCreateChart(crmNamespace()) { return nil, ErrNoCreatePermissions.withStack() } @@ -84,7 +84,7 @@ func (svc *chart) Create(mod *types.Chart) (c *types.Chart, err error) { return svc.chartRepo.Create(mod) } -func (svc *chart) Update(mod *types.Chart) (c *types.Chart, err error) { +func (svc chart) Update(mod *types.Chart) (c *types.Chart, err error) { if mod.ID == 0 { return nil, ErrInvalidID.withStack() } @@ -107,7 +107,7 @@ func (svc *chart) Update(mod *types.Chart) (c *types.Chart, err error) { return svc.chartRepo.Update(c) } -func (svc *chart) DeleteByID(namespaceID, chartID uint64) error { +func (svc chart) DeleteByID(namespaceID, chartID uint64) error { if namespaceID == 0 { return ErrNamespaceRequired.withStack() } diff --git a/compose/internal/service/trigger.go b/compose/internal/service/trigger.go index ac5e51a8a..fba68b15f 100644 --- a/compose/internal/service/trigger.go +++ b/compose/internal/service/trigger.go @@ -3,7 +3,6 @@ package service import ( "context" - "github.com/pkg/errors" "github.com/titpetric/factory" "github.com/crusttech/crust/compose/internal/repository" @@ -18,27 +17,27 @@ type ( prmSvc PermissionsService triggerRepo repository.TriggerRepository - moduleRepo repository.ModuleRepository } TriggerService interface { With(ctx context.Context) TriggerService - FindByID(triggerID uint64) (*types.Trigger, error) - Find(filter types.TriggerFilter) (set types.TriggerSet, err error) + FindByID(namespaceID, triggerID uint64) (*types.Trigger, error) + Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) Create(trigger *types.Trigger) (*types.Trigger, error) Update(trigger *types.Trigger) (*types.Trigger, error) - DeleteByID(triggerID uint64) error + DeleteByID(namespaceID, triggerID uint64) error } ) func Trigger() TriggerService { return (&trigger{ - prmSvc: DefaultPermissions}).With(context.Background()) + prmSvc: DefaultPermissions, + }).With(context.Background()) } -func (svc *trigger) With(ctx context.Context) TriggerService { +func (svc trigger) With(ctx context.Context) TriggerService { db := repository.DB(ctx) return &trigger{ db: db, @@ -47,74 +46,80 @@ func (svc *trigger) With(ctx context.Context) TriggerService { prmSvc: svc.prmSvc.With(ctx), triggerRepo: repository.Trigger(ctx, db), - moduleRepo: repository.Module(ctx, db), } } -func (svc *trigger) FindByID(id uint64) (t *types.Trigger, err error) { - if t, err = svc.triggerRepo.FindByID(id); err != nil { +func (svc trigger) FindByID(namespaceID, triggerID uint64) (c *types.Trigger, err error) { + if namespaceID == 0 { + return nil, ErrNamespaceRequired + } + + if c, err = svc.triggerRepo.FindByID(namespaceID, triggerID); err != nil { return - } else if !svc.prmSvc.CanReadTrigger(t) { - return nil, errors.New("not allowed to access this trigger") + } else if !svc.prmSvc.CanReadTrigger(c) { + return nil, ErrNoReadPermissions.withStack() } return } -func (svc *trigger) Find(filter types.TriggerFilter) (tt types.TriggerSet, err error) { - if tt, err = svc.triggerRepo.Find(filter); err != nil { - return nil, err - } else { - return tt.Filter(func(m *types.Trigger) (bool, error) { - return svc.prmSvc.CanReadTrigger(m), nil - }) +func (svc trigger) Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) { + set, f, err = svc.triggerRepo.Find(filter) + if err != nil { + return } + + set, _ = set.Filter(func(m *types.Trigger) (bool, error) { + return svc.prmSvc.CanReadTrigger(m), nil + }) + + return } -func (svc *trigger) Create(trigger *types.Trigger) (p *types.Trigger, err error) { +func (svc trigger) Create(mod *types.Trigger) (c *types.Trigger, err error) { if !svc.prmSvc.CanCreateTrigger(crmNamespace()) { - return nil, errors.New("not allowed to create this trigger") + return nil, ErrNoCreatePermissions.withStack() } - return p, svc.db.Transaction(func() (err error) { - p, err = svc.triggerRepo.Create(trigger) + return svc.triggerRepo.Create(mod) +} + +func (svc trigger) Update(mod *types.Trigger) (c *types.Trigger, err error) { + if mod.ID == 0 { + return nil, ErrInvalidID.withStack() + } + + if c, err = svc.triggerRepo.FindByID(mod.NamespaceID, mod.ID); err != nil { return - }) -} - -func (svc *trigger) Update(trigger *types.Trigger) (t *types.Trigger, err error) { - validate := func() error { - if trigger.ID == 0 { - return errors.New("Error updating trigger: invalid ID") - } else if t, err = svc.triggerRepo.FindByID(trigger.ID); err != nil { - return errors.Wrap(err, "Error while loading trigger for update") - } else { - if !svc.prmSvc.CanUpdateModule(t) { - return errors.New("not allowed to update this trigger") - } - - trigger.CreatedAt = t.CreatedAt - } - - return nil } - if err := validate(); err != nil { - return nil, err + if isStale(mod.UpdatedAt, c.UpdatedAt, c.CreatedAt) { + return nil, ErrStaleData.withStack() } - return t, svc.db.Transaction(func() (err error) { - t, err = svc.triggerRepo.Update(trigger) - return - }) -} - -func (svc *trigger) DeleteByID(ID uint64) error { - if t, err := svc.triggerRepo.FindByID(ID); err != nil { - return errors.Wrap(err, "could not delete trigger") - } else if !svc.prmSvc.CanDeleteTrigger(t) { - return errors.New("not allowed to delete this trigger") + if !svc.prmSvc.CanUpdateTrigger(c) { + return nil, ErrNoUpdatePermissions.withStack() } - return svc.triggerRepo.DeleteByID(ID) + c.Name = mod.Name + c.ModuleID = mod.ModuleID + c.Source = mod.Source + c.Actions = mod.Actions + c.Enabled = mod.Enabled + + return svc.triggerRepo.Update(c) +} + +func (svc trigger) DeleteByID(namespaceID, triggerID uint64) error { + if namespaceID == 0 { + return ErrNamespaceRequired.withStack() + } + + if c, err := svc.triggerRepo.FindByID(namespaceID, triggerID); err != nil { + return err + } else if !svc.prmSvc.CanDeleteTrigger(c) { + return ErrNoDeletePermissions.withStack() + } + + return svc.triggerRepo.DeleteByID(namespaceID, triggerID) } diff --git a/compose/rest/chart.go b/compose/rest/chart.go index 0dde7f717..65457b9e6 100644 --- a/compose/rest/chart.go +++ b/compose/rest/chart.go @@ -92,18 +92,18 @@ func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface return resputil.OK(), ctrl.chart.With(ctx).DeleteByID(r.NamespaceID, r.ChartID) } -func (ctrl Chart) makePayload(ctx context.Context, ns *types.Chart, err error) (*chartPayload, error) { - if err != nil || ns == nil { +func (ctrl Chart) makePayload(ctx context.Context, t *types.Chart, err error) (*chartPayload, error) { + if err != nil || t == nil { return nil, err } perm := ctrl.permissions.With(ctx) return &chartPayload{ - Chart: ns, + Chart: t, - CanUpdateChart: perm.CanUpdateChart(ns), - CanDeleteChart: perm.CanDeleteChart(ns), + CanUpdateChart: perm.CanUpdateChart(t), + CanDeleteChart: perm.CanDeleteChart(t), }, nil } diff --git a/compose/rest/request/trigger.go b/compose/rest/request/trigger.go index 9042deb69..49fc43252 100644 --- a/compose/rest/request/trigger.go +++ b/compose/rest/request/trigger.go @@ -25,6 +25,8 @@ import ( "github.com/go-chi/chi" "github.com/pkg/errors" + + "time" ) var _ = chi.URLParam @@ -33,6 +35,9 @@ var _ = multipart.FileHeader{} // Trigger list request parameters type TriggerList struct { ModuleID uint64 `json:",string"` + Query string + Page uint + PerPage uint NamespaceID uint64 `json:",string"` } @@ -71,6 +76,18 @@ func (tReq *TriggerList) Fill(r *http.Request) (err error) { tReq.ModuleID = parseUInt64(val) } + if val, ok := get["query"]; ok { + + tReq.Query = val + } + if val, ok := get["page"]; ok { + + tReq.Page = parseUint(val) + } + if val, ok := get["perPage"]; ok { + + tReq.PerPage = parseUint(val) + } tReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err @@ -85,6 +102,7 @@ type TriggerCreate struct { Actions []string Enabled bool Source string + UpdatedAt *time.Time NamespaceID uint64 `json:",string"` } @@ -135,6 +153,12 @@ func (tReq *TriggerCreate) Fill(r *http.Request) (err error) { tReq.Source = val } + if val, ok := post["updatedAt"]; ok { + + if tReq.UpdatedAt, err = parseISODatePtrWithErr(val); err != nil { + return err + } + } tReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err diff --git a/compose/rest/trigger.go b/compose/rest/trigger.go index cee170569..7921ba2a5 100644 --- a/compose/rest/trigger.go +++ b/compose/rest/trigger.go @@ -8,67 +8,114 @@ import ( "github.com/crusttech/crust/compose/internal/service" "github.com/crusttech/crust/compose/rest/request" "github.com/crusttech/crust/compose/types" - - "github.com/pkg/errors" ) -var _ = errors.Wrap +type ( + triggerPayload struct { + *types.Trigger -type Trigger struct { - trigger service.TriggerService -} + CanUpdateTrigger bool `json:"canUpdateTrigger"` + CanDeleteTrigger bool `json:"canDeleteTrigger"` + } + + triggerSetPayload struct { + Filter types.TriggerFilter `json:"filter"` + Set []*triggerPayload `json:"set"` + } + + Trigger struct { + trigger service.TriggerService + permissions service.PermissionsService + } +) func (Trigger) New() *Trigger { return &Trigger{ - trigger: service.DefaultTrigger, + trigger: service.DefaultTrigger, + permissions: service.DefaultPermissions, } } -func (ctrl *Trigger) List(ctx context.Context, r *request.TriggerList) (interface{}, error) { - filter := types.TriggerFilter{} - - if r.ModuleID > 0 { - filter.ModuleID = r.ModuleID +func (ctrl Trigger) List(ctx context.Context, r *request.TriggerList) (interface{}, error) { + f := types.TriggerFilter{ + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, } - return ctrl.trigger.With(ctx).Find(filter) + set, filter, err := ctrl.trigger.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, set, filter, err) } -func (ctrl *Trigger) Create(ctx context.Context, r *request.TriggerCreate) (interface{}, error) { - trigger := &types.Trigger{ - Name: r.Name, - Actions: r.Actions, - Enabled: r.Enabled, - Source: r.Source, +func (ctrl Trigger) Create(ctx context.Context, r *request.TriggerCreate) (interface{}, error) { + var err error + ns := &types.Trigger{ + NamespaceID: r.NamespaceID, + ModuleID: r.ModuleID, + Name: r.Name, + Actions: r.Actions, + Enabled: r.Enabled, + Source: r.Source, } - if r.ModuleID > 0 { - trigger.ModuleID = r.ModuleID - } - - return ctrl.trigger.With(ctx).Create(trigger) + ns, err = ctrl.trigger.With(ctx).Create(ns) + return ctrl.makePayload(ctx, ns, err) } -func (ctrl *Trigger) Read(ctx context.Context, r *request.TriggerRead) (interface{}, error) { - return ctrl.trigger.With(ctx).FindByID(r.TriggerID) +func (ctrl Trigger) Read(ctx context.Context, r *request.TriggerRead) (interface{}, error) { + return ctrl.trigger.With(ctx).FindByID(r.NamespaceID, r.TriggerID) } -func (ctrl *Trigger) Update(ctx context.Context, r *request.TriggerUpdate) (interface{}, error) { - svc := ctrl.trigger.With(ctx) +func (ctrl Trigger) Update(ctx context.Context, r *request.TriggerUpdate) (interface{}, error) { + var ( + mod = &types.Trigger{} + err error + ) - if trigger, err := svc.FindByID(r.TriggerID); err != nil { + mod.ModuleID = r.ModuleID + mod.Name = r.Name + mod.Actions = r.Actions + mod.Enabled = r.Enabled + mod.Source = r.Source + + mod, err = ctrl.trigger.With(ctx).Update(mod) + return ctrl.makePayload(ctx, mod, err) +} + +func (ctrl Trigger) Delete(ctx context.Context, r *request.TriggerDelete) (interface{}, error) { + _, err := ctrl.trigger.With(ctx).FindByID(r.NamespaceID, r.TriggerID) + if err != nil { return nil, err - } else { - trigger.Name = r.Name - trigger.Actions = r.Actions - trigger.Enabled = r.Enabled - trigger.Source = r.Source - trigger.ModuleID = r.ModuleID - - return svc.Update(trigger) } + + return resputil.OK(), ctrl.trigger.With(ctx).DeleteByID(r.NamespaceID, r.TriggerID) } -func (ctrl *Trigger) Delete(ctx context.Context, r *request.TriggerDelete) (interface{}, error) { - return resputil.OK(), ctrl.trigger.With(ctx).DeleteByID(r.TriggerID) +func (ctrl Trigger) makePayload(ctx context.Context, t *types.Trigger, err error) (*triggerPayload, error) { + if err != nil || t == nil { + return nil, err + } + + perm := ctrl.permissions.With(ctx) + + return &triggerPayload{ + Trigger: t, + + CanUpdateTrigger: perm.CanUpdateTrigger(t), + CanDeleteTrigger: perm.CanDeleteTrigger(t), + }, nil +} + +func (ctrl Trigger) makeFilterPayload(ctx context.Context, nn types.TriggerSet, f types.TriggerFilter, err error) (*triggerSetPayload, error) { + if err != nil { + return nil, err + } + + nsp := &triggerSetPayload{Filter: f, Set: make([]*triggerPayload, len(nn))} + + for i := range nn { + nsp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + } + + return nsp, nil } diff --git a/compose/types/trigger.go b/compose/types/trigger.go index 59a389be3..358077741 100644 --- a/compose/types/trigger.go +++ b/compose/types/trigger.go @@ -26,6 +26,12 @@ type ( } TriggerFilter struct { + NamespaceID uint64 `json:"namespaceID,string"` + Query string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + // Sort string `json:"sort"` + Count uint `json:"count"` ModuleID uint64 `json:"moduleID,string"` } ) diff --git a/docs/compose/README.md b/docs/compose/README.md index f9e06862e..96e5814ee 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -774,6 +774,9 @@ Compose Triggers | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | moduleID | uint64 | GET | Filter triggers by module | N/A | NO | +| query | string | GET | Search query | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Create trigger @@ -793,6 +796,7 @@ Compose Triggers | actions | []string | POST | Actions that trigger this trigger | N/A | NO | | enabled | bool | POST | Enabled | N/A | NO | | source | string | POST | Trigger source code | N/A | NO | +| updatedAt | *time.Time | POST | Last update (or creation) date | N/A | NO | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Get trigger details From edee0543929cd16259ee588296fb97ec35c0bdfd Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sun, 28 Apr 2019 15:28:03 +0200 Subject: [PATCH 08/14] Apply namespace changes to all layers of pages --- api/compose/spec.json | 18 +++ api/compose/spec/page.json | 18 +++ compose/db/mysql/static.go | 2 +- .../20190428080000.page-timestamps.up.sql | 6 + compose/internal/repository/chart.go | 10 +- compose/internal/repository/module.go | 6 +- compose/internal/repository/page.go | 147 +++++++++++++----- compose/internal/repository/trigger.go | 10 +- compose/internal/service/attachment.go | 4 +- compose/internal/service/page.go | 74 +++++---- compose/internal/service/page_test.go | 8 - compose/rest/chart.go | 38 ++--- compose/rest/page.go | 123 +++++++++++---- compose/rest/record.go | 1 - compose/rest/request/page.go | 15 ++ compose/types/page.go | 18 ++- docs/compose/README.md | 3 + 17 files changed, 347 insertions(+), 154 deletions(-) create mode 100644 compose/db/schema/mysql/20190428080000.page-timestamps.up.sql diff --git a/api/compose/spec.json b/api/compose/spec.json index c34e4037d..4761c1600 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -219,6 +219,24 @@ "name": "selfID", "required": false, "title": "Parent page ID" + }, + { + "type": "string", + "name": "query", + "required": false, + "title": "Search query" + }, + { + "name": "page", + "type": "uint", + "required": false, + "title": "Page number (0 based)" + }, + { + "name": "perPage", + "type": "uint", + "required": false, + "title": "Returned items per page (default 50)" } ] } diff --git a/api/compose/spec/page.json b/api/compose/spec/page.json index 9f70840c7..ab63344c9 100644 --- a/api/compose/spec/page.json +++ b/api/compose/spec/page.json @@ -35,6 +35,24 @@ "required": false, "title": "Parent page ID", "type": "uint64" + }, + { + "name": "query", + "required": false, + "title": "Search query", + "type": "string" + }, + { + "name": "page", + "required": false, + "title": "Page number (0 based)", + "type": "uint" + }, + { + "name": "perPage", + "required": false, + "title": "Returned items per page (default 50)", + "type": "uint" } ] } diff --git a/compose/db/mysql/static.go b/compose/db/mysql/static.go index f0543a7b4..915d95ddb 100644 --- a/compose/db/mysql/static.go +++ b/compose/db/mysql/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package mysql -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO compose_namespace (id, name, slug, enabled, meta) VALUES (88714882739863655, 'Crust CRM', 'crm', true, '{}');\n\nALTER TABLE `compose_attachment`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_chart`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_module`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_page`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_trigger`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record_value`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `record_id`,\n ADD INDEX (`rel_namespace`);\n\nUPDATE `compose_attachment` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_chart` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_module` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_page` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_trigger` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record_value` SET `rel_namespace` = 88714882739863655;\n\n\nALTER TABLE `compose_attachment`\n ADD CONSTRAINT `compose_attachment_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_chart`\n ADD CONSTRAINT `compose_chart_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_module`\n ADD CONSTRAINT `compose_module_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_page`\n ADD CONSTRAINT `compose_page_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record`\n ADD CONSTRAINT `compose_record_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_trigger`\n ADD CONSTRAINT `compose_trigger_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record_value`\n ADD CONSTRAINT `compose_record_value_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\nPK\x07\x08\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81v)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\",\x00\x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81`7\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\x1d9\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x16\x00\x16\x00'\x08\x00\x00\x899\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO compose_namespace (id, name, slug, enabled, meta) VALUES (88714882739863655, 'Crust CRM', 'crm', true, '{}');\n\nALTER TABLE `compose_attachment`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_chart`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_module`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_page`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_trigger`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record_value`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `record_id`,\n ADD INDEX (`rel_namespace`);\n\nUPDATE `compose_attachment` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_chart` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_module` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_page` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_trigger` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record_value` SET `rel_namespace` = 88714882739863655;\n\n\nALTER TABLE `compose_attachment`\n ADD CONSTRAINT `compose_attachment_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_chart`\n ADD CONSTRAINT `compose_chart_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_module`\n ADD CONSTRAINT `compose_module_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_page`\n ADD CONSTRAINT `compose_page_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record`\n ADD CONSTRAINT `compose_record_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_trigger`\n ADD CONSTRAINT `compose_trigger_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record_value`\n ADD CONSTRAINT `compose_record_value_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\nPK\x07\x08\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `compose_page`\n ADD COLUMN `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n ADD COLUMN `updated_at` DATETIME DEFAULT NULL,\n ADD COLUMN `deleted_at` DATETIME DEFAULT NULL;\nPK\x07\x08\xbds\x93}\xca\x00\x00\x00\xca\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81v)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\",\x00\x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xbds\x93}\xca\x00\x00\x00\xca\x00\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81`7\x00\x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x868\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81C:\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x17\x00\x17\x00\x83\x08\x00\x00\xaf:\x00\x00\x00\x00" diff --git a/compose/db/schema/mysql/20190428080000.page-timestamps.up.sql b/compose/db/schema/mysql/20190428080000.page-timestamps.up.sql new file mode 100644 index 000000000..e4e43432f --- /dev/null +++ b/compose/db/schema/mysql/20190428080000.page-timestamps.up.sql @@ -0,0 +1,6 @@ +ALTER TABLE `compose_page` + ADD COLUMN `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + ADD COLUMN `updated_at` DATETIME DEFAULT NULL, + ADD COLUMN `deleted_at` DATETIME DEFAULT NULL; + +ALTER TABLE `compose_page` CHANGE COLUMN `module_id` `rel_module` BIGINT UNSIGNED NOT NULL DEFAULT 0; diff --git a/compose/internal/repository/chart.go b/compose/internal/repository/chart.go index 00dfcf63a..e068426a0 100644 --- a/compose/internal/repository/chart.go +++ b/compose/internal/repository/chart.go @@ -14,11 +14,11 @@ type ( ChartRepository interface { With(ctx context.Context, db *factory.DB) ChartRepository - FindByID(namespaceID, attachmentID uint64) (*types.Chart, error) + FindByID(namespaceID, chartID uint64) (*types.Chart, error) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) Create(mod *types.Chart) (*types.Chart, error) Update(mod *types.Chart) (*types.Chart, error) - DeleteByID(namespaceID, attachmentID uint64) error + DeleteByID(namespaceID, chartID uint64) error } chart struct { @@ -81,7 +81,7 @@ func (r chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.Chart query := r.query() if filter.NamespaceID > 0 { - query = query.Where("a.rel_namespace = ?", filter.NamespaceID) + query = query.Where("rel_namespace = ?", filter.NamespaceID) } if f.Query != "" { @@ -113,11 +113,11 @@ func (r chart) Update(mod *types.Chart) (*types.Chart, error) { return mod, r.db().Replace(r.table(), mod) } -func (r chart) DeleteByID(namespaceID, attachmentID uint64) error { +func (r chart) DeleteByID(namespaceID, chartID uint64) error { _, err := r.db().Exec( "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", namespaceID, - attachmentID, + chartID, ) return err diff --git a/compose/internal/repository/module.go b/compose/internal/repository/module.go index 1d377e126..117d4b2f1 100644 --- a/compose/internal/repository/module.go +++ b/compose/internal/repository/module.go @@ -85,7 +85,7 @@ func (r module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.Mo query := r.query() if filter.NamespaceID > 0 { - query = query.Where("a.rel_namespace = ?", filter.NamespaceID) + query = query.Where("rel_namespace = ?", filter.NamespaceID) } if f.Query != "" { @@ -143,11 +143,11 @@ func (r module) updateFields(moduleID uint64, ff types.ModuleFieldSet) error { return nil } -func (r module) DeleteByID(namespaceID, attachmentID uint64) error { +func (r module) DeleteByID(namespaceID, moduleID uint64) error { _, err := r.db().Exec( "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", namespaceID, - attachmentID, + moduleID, ) return err diff --git a/compose/internal/repository/page.go b/compose/internal/repository/page.go index bf06d2ae3..341f99a0a 100644 --- a/compose/internal/repository/page.go +++ b/compose/internal/repository/page.go @@ -2,8 +2,10 @@ package repository import ( "context" + "time" "github.com/titpetric/factory" + "gopkg.in/Masterminds/squirrel.v1" "github.com/crusttech/crust/compose/types" ) @@ -12,17 +14,15 @@ type ( PageRepository interface { With(ctx context.Context, db *factory.DB) PageRepository - FindByID(id uint64) (*types.Page, error) - FindByModuleID(id uint64) (*types.Page, error) - FindBySelfID(selfID uint64) (types.PageSet, error) - Find() (types.PageSet, error) - FindRecordPages() (types.PageSet, error) + FindByID(namespaceID, pageID uint64) (*types.Page, error) + FindByModuleID(namespaceID, pageID uint64) (*types.Page, error) + Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) Create(mod *types.Page) (*types.Page, error) Update(mod *types.Page) (*types.Page, error) - DeleteByID(id uint64) error + DeleteByID(namespaceID, pageID uint64) error - Reorder(selfID uint64, pageIDs []uint64) error + Reorder(namespaceID, selfID uint64, pageIDs []uint64) error } page struct { @@ -30,51 +30,108 @@ type ( } ) +const ( + ErrPageNotFound = repositoryError("PageNotFound") +) + func Page(ctx context.Context, db *factory.DB) PageRepository { return (&page{}).With(ctx, db) } -func (r *page) With(ctx context.Context, db *factory.DB) PageRepository { +func (r page) With(ctx context.Context, db *factory.DB) PageRepository { return &page{ repository: r.repository.With(ctx, db), } } -func (r *page) FindByID(id uint64) (*types.Page, error) { - page := &types.Page{} - if err := r.db().Get(page, "SELECT * FROM compose_page WHERE id=?", id); err != nil { - return page, err +func (r page) table() string { + return "compose_page" +} + +func (r page) columns() []string { + return []string{ + "id", "rel_namespace", "self_id", "rel_module", "title", + "blocks", "description", "visible", "weight", + "created_at", "updated_at", "deleted_at", } - return page, nil } -func (r *page) FindByModuleID(id uint64) (*types.Page, error) { - page := &types.Page{} - if err := r.db().Get(page, "SELECT * FROM compose_page WHERE module_id=?", id); err != nil { - return nil, err +func (r page) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table()). + Where("deleted_at IS NULL") +} + +func (r page) FindByID(namespaceID, pageID uint64) (*types.Page, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("id = ?", pageID) + + c = &types.Page{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) } - return page, nil + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrPageNotFound) } -func (r *page) FindRecordPages() (set types.PageSet, err error) { - return set, r.db().Select(&set, "SELECT * FROM compose_page WHERE module_id > 0") -} +func (r page) FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("rel_module = ?", moduleID) -func (r *page) FindBySelfID(selfID uint64) (types.PageSet, error) { - pages := types.PageSet{} - if err := r.db().Select(&pages, "SELECT * FROM compose_page WHERE self_id = ? ORDER BY weight ASC", selfID); err != nil { - return pages, err + c = &types.Page{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) } - return pages, nil + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrPageNotFound) } -func (r *page) Find() (set types.PageSet, err error) { - return set, r.db().Select(&set, "SELECT * FROM compose_page ORDER BY self_id, weight ASC") +func (r page) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) { + f = filter + f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50) + + query := r.query() + + if filter.NamespaceID > 0 { + query = query.Where("rel_namespace = ?", filter.NamespaceID) + } + + if filter.ParentID > 0 { + query = query.Where("self_id = ?", filter.ParentID) + } + + if f.Query != "" { + q := "%" + f.Query + "%" + query = query.Where("title LIKE ? OR description LIKE ?", q, q) + } + + if f.Count, err = r.count(query); err != nil || f.Count == 0 { + return + } + + query = query. + Columns(r.columns()...). + OrderBy("weight ASC") + + return set, f, r.fetchPaged(&set, query, f.Page, f.PerPage) } -func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { - pageMap := map[uint64]bool{} - if pages, err := r.FindBySelfID(selfID); err != nil { +func (r page) Reorder(namespaceID, parentID uint64, pageIDs []uint64) error { + var ( + pageMap = map[uint64]bool{} + filter = types.PageFilter{NamespaceID: namespaceID, ParentID: parentID} + ) + + if pages, _, err := r.Find(filter); err != nil { return nil } else { for _, page := range pages { @@ -87,7 +144,7 @@ func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { for _, pageID := range pageIDs { if pageMap[pageID] { pageMap[pageID] = false - if _, err := db.Exec("UPDATE compose_page set weight=? where id=? and self_id=?", weight, pageID, selfID); err != nil { + if _, err := db.Exec("UPDATE compose_page SET weight = ? WHERE id = ? AND self_id = ?", weight, pageID, parentID); err != nil { return err } weight++ @@ -95,7 +152,7 @@ func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { } for pageID, update := range pageMap { if update { - if _, err := db.Exec("UPDATE compose_page set weight=? where id=? and self_id=?", weight, pageID, selfID); err != nil { + if _, err := db.Exec("UPDATE compose_page SET weight = ? WHERE id = ? AND self_id = ?", weight, pageID, parentID); err != nil { return err } weight++ @@ -104,19 +161,25 @@ func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { return nil } -func (r *page) Create(item *types.Page) (*types.Page, error) { - page := &types.Page{} - *page = *item +func (r page) Create(mod *types.Page) (*types.Page, error) { + mod.ID = factory.Sonyflake.NextID() + mod.CreatedAt = time.Now() - page.ID = factory.Sonyflake.NextID() - return page, r.db().Insert("compose_page", page) + return mod, r.db().Insert(r.table(), mod) } -func (r *page) Update(page *types.Page) (*types.Page, error) { - return page, r.db().Replace("compose_page", page) +func (r page) Update(mod *types.Page) (*types.Page, error) { + now := time.Now() + mod.UpdatedAt = &now + return mod, r.db().Replace(r.table(), mod) } -func (r *page) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM compose_page WHERE id=?", id) +func (r page) DeleteByID(namespaceID, pageID uint64) error { + _, err := r.db().Exec( + "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", + namespaceID, + pageID, + ) + return err } diff --git a/compose/internal/repository/trigger.go b/compose/internal/repository/trigger.go index cf3588776..045094d5d 100644 --- a/compose/internal/repository/trigger.go +++ b/compose/internal/repository/trigger.go @@ -14,11 +14,11 @@ type ( TriggerRepository interface { With(ctx context.Context, db *factory.DB) TriggerRepository - FindByID(namespaceID, attachmentID uint64) (*types.Trigger, error) + FindByID(namespaceID, triggerID uint64) (*types.Trigger, error) Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) Create(mod *types.Trigger) (*types.Trigger, error) Update(mod *types.Trigger) (*types.Trigger, error) - DeleteByID(namespaceID, attachmentID uint64) error + DeleteByID(namespaceID, triggerID uint64) error } trigger struct { @@ -82,7 +82,7 @@ func (r trigger) Find(filter types.TriggerFilter) (set types.TriggerSet, f types query := r.query() if filter.NamespaceID > 0 { - query = query.Where("a.rel_namespace = ?", filter.NamespaceID) + query = query.Where("rel_namespace = ?", filter.NamespaceID) } if f.Query != "" { @@ -114,11 +114,11 @@ func (r trigger) Update(mod *types.Trigger) (*types.Trigger, error) { return mod, r.db().Replace(r.table(), mod) } -func (r trigger) DeleteByID(namespaceID, attachmentID uint64) error { +func (r trigger) DeleteByID(namespaceID, triggerID uint64) error { _, err := r.db().Exec( "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", namespaceID, - attachmentID, + triggerID, ) return err diff --git a/compose/internal/service/attachment.go b/compose/internal/service/attachment.go index 0d522fb9f..010cfa3a3 100644 --- a/compose/internal/service/attachment.go +++ b/compose/internal/service/attachment.go @@ -108,7 +108,7 @@ func (svc attachment) Find(filter types.AttachmentFilter) (types.AttachmentSet, } if filter.PageID > 0 { - if _, err := svc.pageSvc.FindByID(filter.PageID); err != nil { + if _, err := svc.pageSvc.FindByID(filter.NamespaceID, filter.PageID); err != nil { return nil, filter, err } } @@ -151,7 +151,7 @@ func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size var currentUserID uint64 = auth.GetIdentityFromContext(svc.ctx).Identity() - if p, err := svc.pageSvc.FindByID(pageID); err != nil { + if p, err := svc.pageSvc.FindByID(namespaceID, pageID); err != nil { return nil, err } else if !svc.prmSvc.CanUpdatePage(p) { return nil, errors.New("not allowed to add attachments to this page") diff --git a/compose/internal/service/page.go b/compose/internal/service/page.go index 9e6869218..579d84094 100644 --- a/compose/internal/service/page.go +++ b/compose/internal/service/page.go @@ -24,18 +24,17 @@ type ( PageService interface { With(ctx context.Context) PageService - FindByID(pageID uint64) (*types.Page, error) - FindByModuleID(moduleID uint64) (*types.Page, error) - FindBySelfID(selfID uint64) (pages types.PageSet, err error) - Find() (pages types.PageSet, err error) - Tree() (pages types.PageSet, err error) - FindRecordPages() (pages types.PageSet, err error) + FindByID(namespaceID, pageID uint64) (*types.Page, error) + FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) + FindBySelfID(namespaceID, selfID uint64) (pages types.PageSet, f types.PageFilter, err error) + Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) + Tree(namespaceID uint64) (pages types.PageSet, err error) Create(page *types.Page) (*types.Page, error) Update(page *types.Page) (*types.Page, error) - DeleteByID(pageID uint64) error + DeleteByID(namespaceID, pageID uint64) error - Reorder(selfID uint64, pageIDs []uint64) error + Reorder(namespaceID, selfID uint64, pageIDs []uint64) error } ) @@ -58,12 +57,12 @@ func (svc *page) With(ctx context.Context) PageService { } } -func (svc *page) FindByID(id uint64) (p *types.Page, err error) { - return svc.checkPermissions(svc.pageRepo.FindByID(id)) +func (svc *page) FindByID(namespaceID, pageID uint64) (p *types.Page, err error) { + return svc.checkPermissions(svc.pageRepo.FindByID(namespaceID, pageID)) } -func (svc *page) FindByModuleID(moduleID uint64) (p *types.Page, err error) { - return svc.checkPermissions(svc.pageRepo.FindByModuleID(moduleID)) +func (svc *page) FindByModuleID(namespaceID, moduleID uint64) (p *types.Page, err error) { + return svc.checkPermissions(svc.pageRepo.FindByModuleID(namespaceID, moduleID)) } func (svc *page) checkPermissions(p *types.Page, err error) (*types.Page, error) { @@ -76,19 +75,27 @@ func (svc *page) checkPermissions(p *types.Page, err error) (*types.Page, error) return p, err } -func (svc *page) FindBySelfID(selfID uint64) (pp types.PageSet, err error) { - return svc.filterPageSet(svc.pageRepo.FindBySelfID(selfID)) +func (svc *page) FindBySelfID(namespaceID, parentID uint64) (pp types.PageSet, f types.PageFilter, err error) { + return svc.filterPageSetByPermission(svc.pageRepo.Find(types.PageFilter{ + NamespaceID: namespaceID, + ParentID: parentID, + })) } -func (svc *page) Find() (pages types.PageSet, err error) { - return svc.filterPageSet(svc.pageRepo.Find()) +func (svc *page) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) { + return svc.filterPageSetByPermission(svc.pageRepo.Find(filter)) } -func (svc *page) Tree() (pages types.PageSet, err error) { - var tree types.PageSet +func (svc *page) Tree(namespaceID uint64) (pages types.PageSet, err error) { + var ( + tree types.PageSet + filter = types.PageFilter{ + NamespaceID: namespaceID, + } + ) return tree, svc.db.Transaction(func() (err error) { - if pages, err = svc.filterPageSet(svc.pageRepo.Find()); err != nil { + if pages, _, err = svc.filterPageSetByPermission(svc.pageRepo.Find(filter)); err != nil { return } @@ -116,22 +123,21 @@ func (svc *page) Tree() (pages types.PageSet, err error) { }) } -func (svc *page) FindRecordPages() (pages types.PageSet, err error) { - return svc.pageRepo.FindRecordPages() -} - -func (svc *page) filterPageSet(pp types.PageSet, err error) (types.PageSet, error) { +func (svc *page) filterPageSetByPermission(pp types.PageSet, f types.PageFilter, err error) (types.PageSet, types.PageFilter, error) { if err != nil { - return nil, err + return nil, f, err } - return pp.Filter(func(m *types.Page) (bool, error) { + // @todo Filter-by-permission can/will mess up filter's count & paging... + pp, err = pp.Filter(func(m *types.Page) (bool, error) { return svc.prmSvc.CanReadPage(m), nil }) + + return pp, f, err } -func (svc *page) Reorder(selfID uint64, pageIDs []uint64) error { - return svc.pageRepo.Reorder(selfID, pageIDs) +func (svc *page) Reorder(namespaceID, selfID uint64, pageIDs []uint64) error { + return svc.pageRepo.Reorder(namespaceID, selfID, pageIDs) } func (svc *page) Create(page *types.Page) (p *types.Page, err error) { @@ -141,7 +147,7 @@ func (svc *page) Create(page *types.Page) (p *types.Page, err error) { } if page.ModuleID > 0 { - if p, err = svc.pageRepo.FindByModuleID(page.ModuleID); err != nil { + if p, err = svc.pageRepo.FindByModuleID(page.NamespaceID, page.ModuleID); err != nil { return err } else if p.ID > 0 { return errors.New("Page for module already exists") @@ -162,7 +168,7 @@ func (svc *page) Update(page *types.Page) (p *types.Page, err error) { validate := func() error { if page.ID == 0 { return errors.New("Error when saving page, invalid ID") - } else if p, err = svc.pageRepo.FindByID(page.ID); err != nil { + } else if p, err = svc.pageRepo.FindByID(page.NamespaceID, page.ID); err != nil { return errors.Wrap(err, "Error while loading page for update") } else { if !svc.prmSvc.CanUpdatePage(p) { @@ -171,7 +177,7 @@ func (svc *page) Update(page *types.Page) (p *types.Page, err error) { } if page.ModuleID > 0 { - if p, err = svc.pageRepo.FindByModuleID(page.ModuleID); err != nil { + if p, err = svc.pageRepo.FindByModuleID(page.NamespaceID, page.ModuleID); err != nil { return err } else if p.ID > 0 && page.ID != p.ID { return errors.New("Page for module already exists") @@ -188,12 +194,12 @@ func (svc *page) Update(page *types.Page) (p *types.Page, err error) { }) } -func (svc *page) DeleteByID(ID uint64) error { - if p, err := svc.pageRepo.FindByID(ID); err != nil { +func (svc *page) DeleteByID(namespaceID, pageID uint64) error { + if p, err := svc.pageRepo.FindByID(namespaceID, pageID); err != nil { return errors.Wrap(err, "could not delete page") } else if !svc.prmSvc.CanDeletePage(p) { return errors.New("not allowed to delete this page") } - return svc.pageRepo.DeleteByID(ID) + return svc.pageRepo.DeleteByID(namespaceID, pageID) } diff --git a/compose/internal/service/page_test.go b/compose/internal/service/page_test.go index 6e4acddcf..0547b0033 100644 --- a/compose/internal/service/page_test.go +++ b/compose/internal/service/page_test.go @@ -106,14 +106,6 @@ func TestPage(t *testing.T) { prevPageCount = len(ms) } - // fetch all record pages - { - ms, err := repository.FindRecordPages() - test.Assert(t, err == nil, "Error when retrieving pages: %+v", err) - test.Assert(t, len(ms) >= 1, "Expected at least one page, got %d", len(ms)) - prevPageCount = len(ms) - } - // fetch all pages { ms, err := repository.FindBySelfID(m.ID) diff --git a/compose/rest/chart.go b/compose/rest/chart.go index 65457b9e6..c351a2d13 100644 --- a/compose/rest/chart.go +++ b/compose/rest/chart.go @@ -53,14 +53,14 @@ func (ctrl Chart) List(ctx context.Context, r *request.ChartList) (interface{}, func (ctrl Chart) Create(ctx context.Context, r *request.ChartCreate) (interface{}, error) { var err error - ns := &types.Chart{ + mod := &types.Chart{ NamespaceID: r.NamespaceID, Name: r.Name, Config: r.Config, } - ns, err = ctrl.chart.With(ctx).Create(ns) - return ctrl.makePayload(ctx, ns, err) + mod, err = ctrl.chart.With(ctx).Create(mod) + return ctrl.makePayload(ctx, mod, err) } func (ctrl Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, error) { @@ -69,18 +69,18 @@ func (ctrl Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, func (ctrl Chart) Update(ctx context.Context, r *request.ChartUpdate) (interface{}, error) { var ( - ns = &types.Chart{} + mod = &types.Chart{} err error ) - ns.ID = r.ChartID - ns.Name = r.Name - ns.Config = r.Config - ns.NamespaceID = r.NamespaceID - ns.UpdatedAt = r.UpdatedAt + mod.ID = r.ChartID + mod.Name = r.Name + mod.Config = r.Config + mod.NamespaceID = r.NamespaceID + mod.UpdatedAt = r.UpdatedAt - ns, err = ctrl.chart.With(ctx).Update(ns) - return ctrl.makePayload(ctx, ns, err) + mod, err = ctrl.chart.With(ctx).Update(mod) + return ctrl.makePayload(ctx, mod, err) } func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface{}, error) { @@ -92,18 +92,18 @@ func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface return resputil.OK(), ctrl.chart.With(ctx).DeleteByID(r.NamespaceID, r.ChartID) } -func (ctrl Chart) makePayload(ctx context.Context, t *types.Chart, err error) (*chartPayload, error) { - if err != nil || t == nil { +func (ctrl Chart) makePayload(ctx context.Context, c *types.Chart, err error) (*chartPayload, error) { + if err != nil || c == nil { return nil, err } perm := ctrl.permissions.With(ctx) return &chartPayload{ - Chart: t, + Chart: c, - CanUpdateChart: perm.CanUpdateChart(t), - CanDeleteChart: perm.CanDeleteChart(t), + CanUpdateChart: perm.CanUpdateChart(c), + CanDeleteChart: perm.CanDeleteChart(c), }, nil } @@ -112,11 +112,11 @@ func (ctrl Chart) makeFilterPayload(ctx context.Context, nn types.ChartSet, f ty return nil, err } - nsp := &chartSetPayload{Filter: f, Set: make([]*chartPayload, len(nn))} + modp := &chartSetPayload{Filter: f, Set: make([]*chartPayload, len(nn))} for i := range nn { - nsp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) } - return nsp, nil + return modp, nil } diff --git a/compose/rest/page.go b/compose/rest/page.go index 8eb346c24..734ec0c32 100644 --- a/compose/rest/page.go +++ b/compose/rest/page.go @@ -12,70 +12,100 @@ import ( ) type ( + pagePayload struct { + *types.Page + + CanUpdatePage bool `json:"canUpdatePage"` + CanDeletePage bool `json:"canDeletePage"` + } + + pageSetPayload struct { + Filter types.PageFilter `json:"filter"` + Set []*pagePayload `json:"set"` + } + Page struct { - page service.PageService - attachment service.AttachmentService + page service.PageService + attachment service.AttachmentService + permissions service.PermissionsService } ) func (Page) New() *Page { return &Page{ - page: service.DefaultPage, - attachment: service.DefaultAttachment, + page: service.DefaultPage, + attachment: service.DefaultAttachment, + permissions: service.DefaultPermissions, } } func (ctrl *Page) List(ctx context.Context, r *request.PageList) (interface{}, error) { - if r.SelfID > 0 { - return ctrl.page.With(ctx).FindBySelfID(r.SelfID) - } else { - return ctrl.page.With(ctx).Find() + f := types.PageFilter{ + NamespaceID: r.NamespaceID, + ParentID: r.SelfID, + + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, } + + set, filter, err := ctrl.page.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, set, filter, err) } func (ctrl *Page) Tree(ctx context.Context, r *request.PageTree) (interface{}, error) { - return ctrl.page.With(ctx).Tree() + return ctrl.page.With(ctx).Tree(r.NamespaceID) } func (ctrl *Page) Create(ctx context.Context, r *request.PageCreate) (interface{}, error) { - p := &types.Page{ - SelfID: r.SelfID, - ModuleID: r.ModuleID, - Title: r.Title, - Description: r.Description, - Blocks: r.Blocks, - Visible: r.Visible, - } - return ctrl.page.With(ctx).Create(p) + var ( + err error + mod = &types.Page{ + NamespaceID: r.NamespaceID, + SelfID: r.SelfID, + ModuleID: r.ModuleID, + Title: r.Title, + Description: r.Description, + Blocks: r.Blocks, + Visible: r.Visible, + } + ) + + mod, err = ctrl.page.With(ctx).Create(mod) + return ctrl.makePayload(ctx, mod, err) } func (ctrl *Page) Read(ctx context.Context, r *request.PageRead) (interface{}, error) { - return ctrl.page.With(ctx).FindByID(r.PageID) + return ctrl.page.With(ctx).FindByID(r.NamespaceID, r.PageID) } func (ctrl *Page) Reorder(ctx context.Context, r *request.PageReorder) (interface{}, error) { - return resputil.OK(), ctrl.page.With(ctx).Reorder(r.SelfID, payload.ParseUInt64s(r.PageIDs)) + return resputil.OK(), ctrl.page.With(ctx).Reorder(r.NamespaceID, r.SelfID, payload.ParseUInt64s(r.PageIDs)) } func (ctrl *Page) Update(ctx context.Context, r *request.PageUpdate) (interface{}, error) { - p := &types.Page{ - ID: r.PageID, - SelfID: r.SelfID, - ModuleID: r.ModuleID, - Title: r.Title, - Description: r.Description, - Blocks: r.Blocks, - Visible: r.Visible, - } - return ctrl.page.With(ctx).Update(p) + var ( + err error + mod = &types.Page{ + ID: r.PageID, + SelfID: r.SelfID, + ModuleID: r.ModuleID, + Title: r.Title, + Description: r.Description, + Blocks: r.Blocks, + Visible: r.Visible, + } + ) + + mod, err = ctrl.page.With(ctx).Update(mod) + return ctrl.makePayload(ctx, mod, err) } func (ctrl *Page) Delete(ctx context.Context, r *request.PageDelete) (interface{}, error) { - return resputil.OK(), ctrl.page.With(ctx).DeleteByID(r.PageID) + return resputil.OK(), ctrl.page.With(ctx).DeleteByID(r.NamespaceID, r.PageID) } func (ctrl *Page) Upload(ctx context.Context, r *request.PageUpload) (interface{}, error) { - // @todo [SECURITY] check if attachments can be added to this page file, err := r.Upload.Open() if err != nil { return nil, err @@ -93,3 +123,32 @@ func (ctrl *Page) Upload(ctx context.Context, r *request.PageUpload) (interface{ return makeAttachmentPayload(ctx, a, err) } + +func (ctrl Page) makePayload(ctx context.Context, c *types.Page, err error) (*pagePayload, error) { + if err != nil || c == nil { + return nil, err + } + + perm := ctrl.permissions.With(ctx) + + return &pagePayload{ + Page: c, + + CanUpdatePage: perm.CanUpdatePage(c), + CanDeletePage: perm.CanDeletePage(c), + }, nil +} + +func (ctrl Page) makeFilterPayload(ctx context.Context, nn types.PageSet, f types.PageFilter, err error) (*pageSetPayload, error) { + if err != nil { + return nil, err + } + + modp := &pageSetPayload{Filter: f, Set: make([]*pagePayload, len(nn))} + + for i := range nn { + modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + } + + return modp, nil +} diff --git a/compose/rest/record.go b/compose/rest/record.go index 13f40e39a..14ce5cc95 100644 --- a/compose/rest/record.go +++ b/compose/rest/record.go @@ -54,7 +54,6 @@ func (ctrl *Record) Delete(ctx context.Context, r *request.RecordDelete) (interf } func (ctrl *Record) Upload(ctx context.Context, r *request.RecordUpload) (interface{}, error) { - // @todo [SECURITY] check if attachments can be added to this page file, err := r.Upload.Open() if err != nil { return nil, err diff --git a/compose/rest/request/page.go b/compose/rest/request/page.go index 15f0e3e95..b9b859e28 100644 --- a/compose/rest/request/page.go +++ b/compose/rest/request/page.go @@ -35,6 +35,9 @@ var _ = multipart.FileHeader{} // Page list request parameters type PageList struct { SelfID uint64 `json:",string"` + Query string + Page uint + PerPage uint NamespaceID uint64 `json:",string"` } @@ -73,6 +76,18 @@ func (pReq *PageList) Fill(r *http.Request) (err error) { pReq.SelfID = parseUInt64(val) } + if val, ok := get["query"]; ok { + + pReq.Query = val + } + if val, ok := get["page"]; ok { + + pReq.Page = parseUint(val) + } + if val, ok := get["perPage"]; ok { + + pReq.PerPage = parseUint(val) + } pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err diff --git a/compose/types/page.go b/compose/types/page.go index b0fdabbb8..0622e49e1 100644 --- a/compose/types/page.go +++ b/compose/types/page.go @@ -1,6 +1,8 @@ package types import ( + "time" + "github.com/jmoiron/sqlx/types" "github.com/crusttech/crust/internal/rules" @@ -14,8 +16,7 @@ type ( NamespaceID uint64 `json:"namespaceID,string" db:"rel_namespace"` - ModuleID uint64 `json:"moduleID,string" db:"module_id"` - Module *Module `json:"module,omitempty" db:"-"` + ModuleID uint64 `json:"moduleID,string" db:"rel_module"` Title string `json:"title" db:"title"` Description string `json:"description" db:"description"` @@ -26,6 +27,10 @@ type ( Visible bool `json:"visible" db:"visible"` Weight int `json:"-" db:"weight"` + + CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` + UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"` + DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` } // Block - value of Page.Blocks ([]Block) @@ -39,6 +44,15 @@ type ( Width int `json:"width"` Height int `json:"height"` } + + PageFilter struct { + NamespaceID uint64 `json:"namespaceID,string"` + ParentID uint64 `json:"paentID,string"` + Query string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + Count uint `json:"count"` + } ) // Resource returns a system resource ID for this type diff --git a/docs/compose/README.md b/docs/compose/README.md index 96e5814ee..45ef5f8c5 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -461,6 +461,9 @@ Compose pages | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | selfID | uint64 | GET | Parent page ID | N/A | NO | +| query | string | GET | Search query | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Create page From 4af79a5f354c1f58f185150ce1252566eeb3ab18 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sun, 28 Apr 2019 19:55:49 +0200 Subject: [PATCH 09/14] Apply namespace changes to all layers of records --- api/compose/spec.json | 4 +- api/compose/spec/record.json | 4 +- compose/db/mysql/static.go | 2 +- .../20190428080000.namespace-refs.up.sql | 10 - compose/internal/repository/record.go | 216 ++++++-------- compose/internal/service/attachment.go | 4 +- compose/internal/service/record.go | 265 +++++++++--------- compose/internal/service/record_test.go | 109 ++++--- compose/rest/record.go | 73 ++++- compose/rest/request/record.go | 8 +- compose/types/record.go | 12 +- docs/compose/README.md | 4 +- 12 files changed, 373 insertions(+), 338 deletions(-) diff --git a/api/compose/spec.json b/api/compose/spec.json index 4761c1600..7fd06455f 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -661,13 +661,13 @@ }, { "name": "page", - "type": "int", + "type": "uint", "required": false, "title": "Page number (0 based)" }, { "name": "perPage", - "type": "int", + "type": "uint", "required": false, "title": "Returned items per page (default 50)" }, diff --git a/api/compose/spec/record.json b/api/compose/spec/record.json index 709f070a4..ebc06d2de 100644 --- a/api/compose/spec/record.json +++ b/api/compose/spec/record.json @@ -74,13 +74,13 @@ "name": "page", "required": false, "title": "Page number (0 based)", - "type": "int" + "type": "uint" }, { "name": "perPage", "required": false, "title": "Returned items per page (default 50)", - "type": "int" + "type": "uint" }, { "name": "sort", diff --git a/compose/db/mysql/static.go b/compose/db/mysql/static.go index 915d95ddb..5d3c5d094 100644 --- a/compose/db/mysql/static.go +++ b/compose/db/mysql/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package mysql -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO compose_namespace (id, name, slug, enabled, meta) VALUES (88714882739863655, 'Crust CRM', 'crm', true, '{}');\n\nALTER TABLE `compose_attachment`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_chart`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_module`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_page`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_trigger`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record_value`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `record_id`,\n ADD INDEX (`rel_namespace`);\n\nUPDATE `compose_attachment` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_chart` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_module` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_page` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_trigger` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record_value` SET `rel_namespace` = 88714882739863655;\n\n\nALTER TABLE `compose_attachment`\n ADD CONSTRAINT `compose_attachment_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_chart`\n ADD CONSTRAINT `compose_chart_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_module`\n ADD CONSTRAINT `compose_module_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_page`\n ADD CONSTRAINT `compose_page_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record`\n ADD CONSTRAINT `compose_record_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_trigger`\n ADD CONSTRAINT `compose_trigger_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record_value`\n ADD CONSTRAINT `compose_record_value_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\nPK\x07\x08\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `compose_page`\n ADD COLUMN `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n ADD COLUMN `updated_at` DATETIME DEFAULT NULL,\n ADD COLUMN `deleted_at` DATETIME DEFAULT NULL;\nPK\x07\x08\xbds\x93}\xca\x00\x00\x00\xca\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81v)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\",\x00\x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xbds\x93}\xca\x00\x00\x00\xca\x00\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81`7\x00\x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x868\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81C:\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x17\x00\x17\x00\x83\x08\x00\x00\xaf:\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO compose_namespace (id, name, slug, enabled, meta) VALUES (88714882739863655, 'Crust CRM', 'crm', true, '{}');\n\nALTER TABLE `compose_attachment`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_chart`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_module`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_page`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_trigger`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nUPDATE `compose_attachment` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_chart` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_module` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_page` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_trigger` SET `rel_namespace` = 88714882739863655;\n\n\nALTER TABLE `compose_attachment`\n ADD CONSTRAINT `compose_attachment_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_chart`\n ADD CONSTRAINT `compose_chart_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_module`\n ADD CONSTRAINT `compose_module_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_page`\n ADD CONSTRAINT `compose_page_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record`\n ADD CONSTRAINT `compose_record_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_trigger`\n ADD CONSTRAINT `compose_trigger_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\nPK\x07\x08\xb2\x05Q\x14R \x00\x00R \x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `compose_page`\n ADD COLUMN `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n ADD COLUMN `updated_at` DATETIME DEFAULT NULL,\n ADD COLUMN `deleted_at` DATETIME DEFAULT NULL;\n\nALTER TABLE `compose_page` CHANGE COLUMN `module_id` `rel_module` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nPK\x07\x08\x82\x01Rn1\x01\x00\x001\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81v)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb2\x05Q\x14R \x00\x00R \x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\",\x00\x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x82\x01Rn1\x01\x00\x001\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xcf5\x00\x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\7\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\x199\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x17\x00\x17\x00\x83\x08\x00\x00\x859\x00\x00\x00\x00" diff --git a/compose/db/schema/mysql/20190428080000.namespace-refs.up.sql b/compose/db/schema/mysql/20190428080000.namespace-refs.up.sql index 26a9287bd..229483efb 100644 --- a/compose/db/schema/mysql/20190428080000.namespace-refs.up.sql +++ b/compose/db/schema/mysql/20190428080000.namespace-refs.up.sql @@ -24,17 +24,12 @@ ALTER TABLE `compose_trigger` ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`, ADD INDEX (`rel_namespace`); -ALTER TABLE `compose_record_value` - ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `record_id`, - ADD INDEX (`rel_namespace`); - UPDATE `compose_attachment` SET `rel_namespace` = 88714882739863655; UPDATE `compose_chart` SET `rel_namespace` = 88714882739863655; UPDATE `compose_module` SET `rel_namespace` = 88714882739863655; UPDATE `compose_page` SET `rel_namespace` = 88714882739863655; UPDATE `compose_record` SET `rel_namespace` = 88714882739863655; UPDATE `compose_trigger` SET `rel_namespace` = 88714882739863655; -UPDATE `compose_record_value` SET `rel_namespace` = 88714882739863655; ALTER TABLE `compose_attachment` @@ -66,8 +61,3 @@ ALTER TABLE `compose_trigger` ADD CONSTRAINT `compose_trigger_namespace` FOREIGN KEY (`rel_namespace`) REFERENCES `compose_namespace` (`id`); - -ALTER TABLE `compose_record_value` - ADD CONSTRAINT `compose_record_value_namespace` - FOREIGN KEY (`rel_namespace`) - REFERENCES `compose_namespace` (`id`); diff --git a/compose/internal/repository/record.go b/compose/internal/repository/record.go index 9719259e1..b80315e58 100644 --- a/compose/internal/repository/record.go +++ b/compose/internal/repository/record.go @@ -6,10 +6,9 @@ import ( "strings" "github.com/jmoiron/sqlx" - "github.com/lann/builder" "github.com/pkg/errors" "github.com/titpetric/factory" - sq "gopkg.in/Masterminds/squirrel.v1" + "gopkg.in/Masterminds/squirrel.v1" "github.com/crusttech/crust/compose/internal/repository/ql" @@ -20,10 +19,10 @@ type ( RecordRepository interface { With(ctx context.Context, db *factory.DB) RecordRepository - FindByID(id uint64) (*types.Record, error) + FindByID(namespaceID, recordID uint64) (*types.Record, error) Report(module *types.Module, metrics, dimensions, filter string) (results interface{}, err error) - Find(module *types.Module, filter string, sort string, page int, perPage int) (*FindResponse, error) + Find(module *types.Module, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) Create(record *types.Record) (*types.Record, error) Update(record *types.Record) (*types.Record, error) @@ -32,25 +31,6 @@ type ( LoadValues(IDs ...uint64) (rvs types.RecordValueSet, err error) DeleteValues(record *types.Record) error UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error) - - CountAuthored(userID uint64) (c int, err error) - ChangeAuthor(userID, target uint64) error - CountReferenced(userID uint64) (c int, err error) - ChangeReferences(userID, target uint64) error - } - - FindResponseMeta struct { - Filter string `json:"filter,omitempty"` - Sort string `json:"sort,omitempty"` - - Page int `json:"page"` - PerPage int `json:"perPage"` - Count int `json:"count"` - } - - FindResponse struct { - Meta FindResponseMeta `json:"meta"` - Records types.RecordSet `json:"records"` } record struct { @@ -59,30 +39,64 @@ type ( ) const ( - sortWrap = `sort` + ErrRecordNotFound = repositoryError("RecordNotFound") ) func Record(ctx context.Context, db *factory.DB) RecordRepository { return (&record{}).With(ctx, db) } -func (r *record) With(ctx context.Context, db *factory.DB) RecordRepository { +func (r record) With(ctx context.Context, db *factory.DB) RecordRepository { return &record{ repository: r.repository.With(ctx, db), } } -// @todo: update to accepted DeletedAt column semantics from Messaging - -func (r *record) FindByID(id uint64) (*types.Record, error) { - mod := &types.Record{} - if err := r.db().Get(mod, "SELECT * FROM compose_record WHERE id=? and deleted_at IS NULL", id); err != nil { - return nil, err - } - return mod, nil +func (r record) table() string { + return "compose_record" } -func (r *record) Report(module *types.Module, metrics, dimensions, filter string) (results interface{}, err error) { +func (r record) columns() []string { + return []string{ + "r.id", + "r.module_id", + "r.rel_namespace", + "r.owned_by", + "r.created_at", + "r.created_by", + "r.updated_at", + "r.updated_by", + "r.deleted_at", + "r.deleted_by", + } +} + +func (r record) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table() + " AS r"). + Where("r.deleted_at IS NULL") +} + +// @todo: update to accepted DeletedAt column semantics from Messaging + +func (r record) FindByID(namespaceID, recordID uint64) (*types.Record, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("id = ?", recordID) + + c = &types.Record{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) + } + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrRecordNotFound) +} + +func (r record) Report(module *types.Module, metrics, dimensions, filter string) (results interface{}, err error) { crb := NewRecordReportBuilder(module) var result = make([]map[string]interface{}, 0) @@ -100,76 +114,31 @@ func (r *record) Report(module *types.Module, metrics, dimensions, filter string } } -func (r *record) Find(module *types.Module, filter string, sort string, page int, perPage int) (*FindResponse, error) { - if page < 0 { - page = 0 - } - if perPage <= 0 { - perPage = 50 - } - if perPage > 100 { - perPage = 100 - } +func (r record) Find(module *types.Module, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) { + var query squirrel.SelectBuilder + f = filter + f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50) - response := &FindResponse{ - Meta: FindResponseMeta{ - Filter: filter, - Page: page, - PerPage: perPage, - Sort: sort, - }, - Records: make([]*types.Record, 0), - } - - var query, err = r.buildQuery(module, filter, sort) + query, err = r.buildQuery(module, filter) if err != nil { - return nil, err + return } - // Assemble SQL for counting (includes only where) - count := query.Column("COUNT(*)") - count = builder.Delete(count, "OrderBys").(sq.SelectBuilder) - sqlSelect, argsSelect, err := count.ToSql() - if err != nil { - return nil, err - } - - // Execute count query. - if err := r.db().Get(&response.Meta.Count, sqlSelect, argsSelect...); err != nil { - return nil, err - } - - // Return empty response if count of records is zero. - if response.Meta.Count == 0 { - return response, nil + if f.Count, err = r.count(query); err != nil || f.Count == 0 { + return } // Assemble SQL for fetching record (where + sorting + paging)... query = query. - Column("r.*"). - Limit(uint64(perPage)). - Offset(uint64(page * perPage)) - - // Create actual fetch SQL sentences. - sqlSelect, argsSelect, err = query.ToSql() - if err != nil { - return nil, err - } - - // append order args to select args and execute actual query. - if err := r.db().Select(&response.Records, sqlSelect, argsSelect...); err != nil { - return nil, err - } - - return response, nil + Columns(r.columns()...) + return set, f, r.fetchPaged(&set, query, f.Page, f.PerPage) } -func (r *record) buildQuery(module *types.Module, filter string, sort string) (query sq.SelectBuilder, err error) { +func (r record) buildQuery(module *types.Module, f types.RecordFilter) (query squirrel.SelectBuilder, err error) { // Create query for fetching and counting records. - query = sq.Select(). - From("compose_record AS r"). - Where(sq.Eq{"r.module_id": module.ID}). - Where(sq.Eq{"r.deleted_at": nil}) + query = r.query(). + Where("r.module_id = ?", module.ID). + Where("r.rel_namespace = ?", module.NamespaceID) var joinedFields = []string{} var alreadyJoined = func(f string) bool { @@ -184,7 +153,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q } // Parse filters. - if filter != "" { + if f.Filter != "" { var ( // Filter parser fp = ql.NewParser() @@ -217,7 +186,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q return i, nil } - if fn, err = fp.ParseExpression(filter); err != nil { + if fn, err = fp.ParseExpression(f.Filter); err != nil { return } else if filterSql, filterArgs, err := fn.ToSql(); err != nil { return query, err @@ -226,7 +195,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q } } - if sort != "" { + if f.Sort != "" { var ( // Sort parser sp = ql.NewParser() @@ -259,7 +228,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q return i, nil } - if sc, err = sp.ParseColumns(sort); err != nil { + if sc, err = sp.ParseColumns(f.Sort); err != nil { return } @@ -270,7 +239,7 @@ func (r *record) buildQuery(module *types.Module, filter string, sort string) (q return } -func (r *record) Create(record *types.Record) (*types.Record, error) { +func (r record) Create(record *types.Record) (*types.Record, error) { record.ID = factory.Sonyflake.NextID() if err := r.db().Replace("compose_record", record); err != nil { @@ -280,7 +249,7 @@ func (r *record) Create(record *types.Record) (*types.Record, error) { return record, nil } -func (r *record) Update(record *types.Record) (*types.Record, error) { +func (r record) Update(record *types.Record) (*types.Record, error) { if err := r.db().Replace("compose_record", record); err != nil { return nil, errors.Wrap(err, "could not update record") } @@ -288,18 +257,19 @@ func (r *record) Update(record *types.Record) (*types.Record, error) { return record, nil } -func (r *record) Delete(record *types.Record) error { +func (r record) Delete(record *types.Record) error { _, err := r.db().Exec( - "UPDATE compose_record SET deleted_at = ?, deleted_by = ? WHERE id = ?", + "UPDATE compose_record SET deleted_at = ?, deleted_by = ? WHERE rel_namespace = ? AND id = ?", record.DeletedAt, record.DeletedBy, + record.NamespaceID, record.ID, ) return err } -func (r *record) DeleteValues(record *types.Record) error { +func (r record) DeleteValues(record *types.Record) error { _, err := r.db().Exec( "UPDATE compose_record_value SET deleted_at = ? WHERE record_id = ?", record.DeletedAt, @@ -308,7 +278,7 @@ func (r *record) DeleteValues(record *types.Record) error { return err } -func (r *record) UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error) { +func (r record) UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error) { // Remove all records and prepare to be updated // @todo be more selective and delete only removed values if _, err = r.db().Exec("DELETE FROM compose_record_value WHERE record_id = ?", recordID); err != nil { @@ -324,12 +294,16 @@ func (r *record) UpdateValues(recordID uint64, rvs types.RecordValueSet) (err er } -func (r *record) LoadValues(IDs ...uint64) (rvs types.RecordValueSet, err error) { +func (r record) LoadValues(IDs ...uint64) (rvs types.RecordValueSet, err error) { if len(IDs) == 0 { return } - var sql = "SELECT * FROM compose_record_value WHERE record_id IN (?) AND deleted_at IS NULL ORDER BY record_id, place" + var sql = "SELECT record_id, name, value, ref, place, deleted_at " + + " FROM compose_record_value " + + " WHERE record_id IN (?) " + + " AND deleted_at IS NULL " + + " ORDER BY record_id, place" if sql, args, err := sqlx.In(sql, IDs); err != nil { return nil, err @@ -367,35 +341,3 @@ func isRealRecordCol(name string) (string, bool) { return name, false } - -func (r *record) CountAuthored(userID uint64) (c int, err error) { - return c, r.db().Get(&c, - "SELECT COUNT(*) FROM compose_record WHERE created_by = ? OR updated_by = ? OR deleted_by = ?", - userID, userID, userID) -} - -func (r *record) ChangeAuthor(userID, target uint64) error { - if _, err := r.db().Exec("UPDATE compose_record SET created_by = ? WHERE created_by = ?", target, userID); err != nil { - return err - } - if _, err := r.db().Exec("UPDATE compose_record SET updated_by = ? WHERE updated_by = ?", target, userID); err != nil { - return err - } - if _, err := r.db().Exec("UPDATE compose_record SET deleted_by = ? WHERE deleted_by = ?", target, userID); err != nil { - return err - } - - return nil -} - -func (r *record) CountReferenced(userID uint64) (c int, err error) { - // @todo add field type (User) check - return c, r.db().Get(&c, - "SELECT COUNT(*) FROM compose_record_value WHERE ref = ?", - userID) -} - -func (r *record) ChangeReferences(userID, target uint64) error { - _, err := r.db().Exec("UPDATE compose_record_value SET ref = ? WHERE ref = ?", target, userID) - return err -} diff --git a/compose/internal/service/attachment.go b/compose/internal/service/attachment.go index 010cfa3a3..0405e7c66 100644 --- a/compose/internal/service/attachment.go +++ b/compose/internal/service/attachment.go @@ -120,7 +120,7 @@ func (svc attachment) Find(filter types.AttachmentFilter) (types.AttachmentSet, } if filter.RecordID > 0 { - if _, err := svc.recordSvc.FindByID(filter.RecordID); err != nil { + if _, err := svc.recordSvc.FindByID(filter.NamespaceID, filter.RecordID); err != nil { return nil, filter, err } } @@ -176,7 +176,7 @@ func (svc attachment) CreateRecordAttachment(namespaceID uint64, name string, si if _, err := svc.moduleSvc.FindByID(namespaceID, moduleID); err != nil { return nil, err - } else if r, err := svc.recordSvc.FindByID(recordID); err != nil { + } else if r, err := svc.recordSvc.FindByID(namespaceID, recordID); err != nil { return nil, err } else if !svc.prmSvc.CanUpdateRecord(r) { return nil, errors.New("not allowed to add attachments to this record") diff --git a/compose/internal/service/record.go b/compose/internal/service/record.go index 6e70bb8a9..2df3694aa 100644 --- a/compose/internal/service/record.go +++ b/compose/internal/service/record.go @@ -23,22 +23,22 @@ type ( prmSvc PermissionsService userSvc systemService.UserService - repository repository.RecordRepository + recordRepo repository.RecordRepository moduleRepo repository.ModuleRepository } RecordService interface { With(ctx context.Context) RecordService - FindByID(recordID uint64) (*types.Record, error) + FindByID(namespaceID, recordID uint64) (*types.Record, error) - Report(moduleID uint64, metrics, dimensions, filter string) (interface{}, error) - Find(moduleID uint64, filter string, sort string, page int, perPage int) (*repository.FindResponse, error) + Report(namespaceID, moduleID uint64, metrics, dimensions, filter string) (interface{}, error) + Find(filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) Create(record *types.Record) (*types.Record, error) Update(record *types.Record) (*types.Record, error) - DeleteByID(recordID uint64) error + DeleteByID(namespaceID, recordID uint64) error // Fields(module *types.Module, record *types.Record) ([]*types.RecordValue, error) } @@ -60,17 +60,111 @@ func (svc *record) With(ctx context.Context) RecordService { prmSvc: svc.prmSvc.With(ctx), userSvc: systemService.User(ctx), - repository: repository.Record(ctx, db), + recordRepo: repository.Record(ctx, db), moduleRepo: repository.Module(ctx, db), } } -func (svc *record) FindByID(recordID uint64) (r *types.Record, err error) { - err = svc.db.Transaction(func() (err error) { - if r, err = svc.repository.FindByID(recordID); err != nil { +func (svc *record) FindByID(namespaceID, recordID uint64) (r *types.Record, err error) { + if namespaceID == 0 { + return nil, ErrNamespaceRequired + } + + if r, err = svc.recordRepo.FindByID(namespaceID, recordID); err != nil { + return + } + + if !svc.prmSvc.CanReadRecord(r) { + return nil, ErrNoReadPermissions.withStack() + } + + if err = svc.preloadValues(r); err != nil { + return + } + + return +} + +func (svc record) loadModule(namespaceID, moduleID uint64) (m *types.Module, err error) { + if m, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { + return + } + + if m.Fields, err = svc.moduleRepo.FindFields(m.ID); err != nil { + return + } + + if !svc.prmSvc.CanReadRecord(m) { + return nil, ErrNoReadPermissions.withStack() + } + + return +} + +func (svc *record) Report(namespaceID, moduleID uint64, metrics, dimensions, filter string) (out interface{}, err error) { + var m *types.Module + if m, err = svc.loadModule(namespaceID, moduleID); err != nil { + return + } + + if !svc.prmSvc.CanReadRecord(m) { + return nil, ErrNoReadPermissions.withStack() + } + + return svc.recordRepo.Report(m, metrics, dimensions, filter) +} + +func (svc *record) Find(filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) { + var m *types.Module + if m, err = svc.loadModule(filter.NamespaceID, filter.ModuleID); err != nil { + return + } + + if !svc.prmSvc.CanReadRecord(m) { + return nil, filter, ErrNoReadPermissions.withStack() + } + + set, f, err = svc.recordRepo.Find(m, filter) + if err != nil { + return + } + + if err = svc.preloadValues(set...); err != nil { + return + } + + return +} + +func (svc *record) Create(mod *types.Record) (r *types.Record, err error) { + if mod.NamespaceID == 0 { + return nil, ErrNamespaceRequired + } + + var m *types.Module + if m, err = svc.loadModule(mod.NamespaceID, mod.ModuleID); err != nil { + return + } + + if !svc.prmSvc.CanCreateRecord(m) { + return nil, ErrNoCreatePermissions.withStack() + } + + if err = svc.sanitizeValues(m, mod.Values); err != nil { + return + } + + mod.OwnedBy = auth.GetIdentityFromContext(svc.ctx).Identity() + mod.CreatedBy = mod.OwnedBy + mod.CreatedAt = time.Now() + + return r, svc.db.Transaction(func() (err error) { + if r, err = svc.recordRepo.Create(mod); err != nil { + return + } + + if err = svc.recordRepo.UpdateValues(r.ID, mod.Values); err != nil { return - } else if !svc.prmSvc.CanReadRecord(r) { - return errors.New("not allowed to access this record") } if err = svc.preloadValues(r); err != nil { @@ -79,138 +173,60 @@ func (svc *record) FindByID(recordID uint64) (r *types.Record, err error) { return }) - - return r, errors.Wrap(err, "unable to find record") } -func (svc *record) Report(moduleID uint64, metrics, dimensions, filter string) (out interface{}, err error) { - var module *types.Module - var namespaceID uint64 = 0 +func (svc *record) Update(mod *types.Record) (r *types.Record, err error) { + if mod.ID == 0 { + return nil, ErrInvalidID.withStack() + } - err = svc.db.Transaction(func() (err error) { - if module, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { - return - } else if !svc.prmSvc.CanReadRecord(module) { - return errors.New("not allowed to access this record") - } + if mod.NamespaceID == 0 { + return nil, ErrNamespaceRequired + } - out, err = svc.repository.Report(module, metrics, dimensions, filter) + var m *types.Module + if m, err = svc.loadModule(mod.NamespaceID, mod.ModuleID); err != nil { return - }) + } - return out, errors.Wrap(err, "unable to build a report") -} + if r, err = svc.recordRepo.FindByID(mod.NamespaceID, mod.ID); err != nil { + return + } -func (svc *record) Find(moduleID uint64, filter, sort string, page, perPage int) (rsp *repository.FindResponse, err error) { - var module *types.Module - var namespaceID uint64 = 0 + if !svc.prmSvc.CanUpdateRecord(r) { + return nil, ErrNoUpdatePermissions.withStack() + } - err = svc.db.Transaction(func() (err error) { - if module, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil { - return - } else if !svc.prmSvc.CanReadRecord(module) { - return errors.New("not allowed to access this record") - } + if isStale(mod.UpdatedAt, r.UpdatedAt, r.CreatedAt) { + return nil, ErrStaleData.withStack() + } - if rsp, err = svc.repository.Find(module, filter, sort, page, perPage); err != nil { + if err = svc.sanitizeValues(m, mod.Values); err != nil { + return + } + + now := time.Now() + r.UpdatedAt = &now + r.UpdatedBy = auth.GetIdentityFromContext(svc.ctx).Identity() + + return r, svc.db.Transaction(func() (err error) { + if r, err = svc.recordRepo.Update(r); err != nil { return } - if err = svc.preloadValues(rsp.Records...); err != nil { + if err = svc.recordRepo.UpdateValues(r.ID, mod.Values); err != nil { return } return }) - - return rsp, errors.Wrap(err, "unable to find records") - } -func (svc *record) Create(in *types.Record) (record *types.Record, err error) { - var module *types.Module - - err = svc.db.Transaction(func() (err error) { - if module, err = svc.moduleRepo.FindByID(in.NamespaceID, in.ModuleID); err != nil { - return - } else if !svc.prmSvc.CanCreateRecord(module) { - return errors.New("not allowed to create records for this module") - } - - if err = svc.sanitizeValues(module, in.Values); err != nil { - return - } - - in.OwnedBy = auth.GetIdentityFromContext(svc.ctx).Identity() - in.CreatedBy = in.OwnedBy - in.CreatedAt = time.Now() - if record, err = svc.repository.Create(in); err != nil { - return - } - - if err = svc.repository.UpdateValues(record.ID, in.Values); err != nil { - return - } - - if err = svc.preloadValues(record); err != nil { - return - } - - return - }) - - return record, errors.Wrap(err, "unable to create record") -} - -func (svc *record) Update(updated *types.Record) (record *types.Record, err error) { - var module *types.Module - - err = svc.db.Transaction(func() (err error) { - if updated.ID == 0 { - return errors.New("invalid record ID") - } - - if record, err = svc.repository.FindByID(updated.ID); err != nil { - return errors.Wrap(err, "nonexistent record") - } else if !svc.prmSvc.CanUpdateRecord(record) { - return errors.New("not allowed to update this record") - } - - if module, err = svc.moduleRepo.FindByID(updated.NamespaceID, updated.ModuleID); err != nil { - return - } - - if err = svc.sanitizeValues(module, updated.Values); err != nil { - return - } - - now := time.Now() - record.UpdatedAt = &now - record.UpdatedBy = auth.GetIdentityFromContext(svc.ctx).Identity() - - if record, err = svc.repository.Update(record); err != nil { - return - } - - if err = svc.repository.UpdateValues(record.ID, updated.Values); err != nil { - return - } - - return - }) - - return record, errors.Wrap(err, "unable to update record") -} - -// func (s *record) Fields(module *types.Module, record *types.Record) ([]*types.RecordValue, error) { -// return s.repository.Fields(module, record) -// } - -func (svc *record) DeleteByID(ID uint64) (err error) { +func (svc *record) DeleteByID(namespaceID, recordID uint64) (err error) { err = svc.db.Transaction(func() (err error) { var record *types.Record - if record, err = svc.repository.FindByID(ID); err != nil { + if record, err = svc.recordRepo.FindByID(namespaceID, recordID); err != nil { return errors.Wrap(err, "nonexistent record") } @@ -218,11 +234,11 @@ func (svc *record) DeleteByID(ID uint64) (err error) { record.DeletedAt = &now record.DeletedBy = auth.GetIdentityFromContext(svc.ctx).Identity() - if err = svc.repository.Delete(record); err != nil { + if err = svc.recordRepo.Delete(record); err != nil { return } - if err = svc.repository.DeleteValues(record); err != nil { + if err = svc.recordRepo.DeleteValues(record); err != nil { return } @@ -247,7 +263,6 @@ func (svc *record) sanitizeValues(module *types.Module, values types.RecordValue } var places = map[string]uint{} - // var has bool return values.Walk(func(value *types.RecordValue) (err error) { var field = module.Fields.FindByName(value.Name) @@ -269,7 +284,7 @@ func (svc *record) sanitizeValues(module *types.Module, values types.RecordValue } func (svc *record) preloadValues(rr ...*types.Record) error { - if rvs, err := svc.repository.LoadValues(types.RecordSet(rr).IDs()...); err != nil { + if rvs, err := svc.recordRepo.LoadValues(types.RecordSet(rr).IDs()...); err != nil { return err } else { return types.RecordSet(rr).Walk(func(r *types.Record) error { diff --git a/compose/internal/service/record_test.go b/compose/internal/service/record_test.go index a0c8ee26e..090539900 100644 --- a/compose/internal/service/record_test.go +++ b/compose/internal/service/record_test.go @@ -23,6 +23,9 @@ func TestRecord(t *testing.T) { ctx = auth.SetIdentityToContext(ctx, auth.NewIdentity(user.Identity())) + var err error + ns1, ns2 := createTestNamespaces(ctx, t) + { userSvc := systemService.TestUser(t, ctx) _, err := userSvc.Create(user) @@ -31,8 +34,9 @@ func TestRecord(t *testing.T) { svc := Record().With(ctx) - module := &types.Module{ - Name: "Test", + module1 := &types.Module{ + NamespaceID: ns1.ID, + Name: "Test", Fields: types.ModuleFieldSet{ &types.ModuleField{ Name: "name", @@ -54,18 +58,27 @@ func TestRecord(t *testing.T) { }, } - // set up a module - var err error - module, err = Module().With(ctx).Create(module) - test.Assert(t, err == nil, "Error when creating module: %+v", err) - test.Assert(t, module.ID > 0, "Expected auto generated ID") + // set up a module1 + module1, err = Module().With(ctx).Create(module1) + test.Assert(t, err == nil, "Error when creating module1: %+v", err) + test.Assert(t, module1.ID > 0, "Expected auto generated ID") + + module2 := &types.Module{ + NamespaceID: ns2.ID, + Name: "Test Dummy", + Fields: module1.Fields, + } + module2, err = Module().With(ctx).Create(module2) + test.Assert(t, err == nil, "Error when creating module1 in another namespace: %+v", err) record1 := &types.Record{ - ModuleID: module.ID, + NamespaceID: ns1.ID, + ModuleID: module1.ID, } record2 := &types.Record{ - ModuleID: module.ID, + NamespaceID: ns1.ID, + ModuleID: module1.ID, Values: types.RecordValueSet{ &types.RecordValue{ Name: "name", @@ -98,9 +111,19 @@ func TestRecord(t *testing.T) { }, } + { + // Let's put something to another namespace... + _, err := svc.Create(&types.Record{ + NamespaceID: ns2.ID, + ModuleID: module2.ID, + }) + test.Assert(t, err == nil, "Error when creating record: %+v", err) + } + // now work with records { { + record1.UpdatedAt = nil m, err := svc.Update(record1) test.Assert(t, m == nil, "Expected empty return for invalid update, got %#v", m) test.Assert(t, err != nil, "Expected error when updating invalid record") @@ -118,7 +141,7 @@ func TestRecord(t *testing.T) { // fetch created record { - ms, err := svc.FindByID(m1.ID) + ms, err := svc.FindByID(m1.NamespaceID, m1.ID) test.Assert(t, err == nil, "Error when retrieving record by id: %+v", err) test.Assert(t, ms.ID == m1.ID, "Expected ID from database to match, %d != %d", m1.ID, ms.ID) test.Assert(t, ms.ModuleID == m1.ModuleID, "Expected Module ID from database to match, %d != %d", m1.ModuleID, ms.ModuleID) @@ -126,13 +149,14 @@ func TestRecord(t *testing.T) { // update created record { + m1.UpdatedAt = nil _, err := svc.Update(m1) test.Assert(t, err == nil, "Error when updating record, %+v", err) } // re-fetch record { - ms, err := svc.FindByID(m1.ID) + ms, err := svc.FindByID(m1.NamespaceID, m1.ID) test.Assert(t, err == nil, "Error when retrieving record by id: %+v", err) test.Assert(t, ms.ID == m1.ID, "Expected ID from database to match, %d != %d", m1.ID, ms.ID) test.Assert(t, ms.ModuleID == m1.ModuleID, "Expected ID from database to match, %d != %d", m1.ModuleID, ms.ModuleID) @@ -140,39 +164,40 @@ func TestRecord(t *testing.T) { // fetch all records { - mr, err := svc.Find(module.ID, "", "id desc", 0, 20) + mr, f, err := svc.Find(types.RecordFilter{ModuleID: module1.ID, Sort: "id desc"}) test.Assert(t, err == nil, "Error when retrieving records: %+v", err) - test.Assert(t, len(mr.Records) == 2, "Expected two record, got %d", len(mr.Records)) - test.Assert(t, mr.Meta.Count == 2, "Expected Meta.Count == 2, got %d", mr.Meta.Count) - test.Assert(t, mr.Meta.Sort == "id desc", "Expected Meta.Sort == id desc, got '%s'", mr.Meta.Sort) - test.Assert(t, mr.Records[0].ModuleID == m1.ModuleID, "Expected record module to match, %d != %d", m1.ModuleID, mr.Records[0].ModuleID) - test.Assert(t, mr.Records[0].ID > mr.Records[1].ID, "Expected order to be descending") + test.Assert(t, len(mr) == 2, "Expected two record, got %d", len(mr)) + test.Assert(t, f.Count == 2, "Expected Meta.Count == 2, got %d", f.Count) + test.Assert(t, f.Sort == "id desc", "Expected Meta.Sort == id desc, got '%s'", f.Sort) + test.Assert(t, mr[0].ModuleID == m1.ModuleID, "Expected record module1 to match, %d != %d", m1.ModuleID, mr[0].ModuleID) + test.Assert(t, mr[0].ID > mr[1].ID, "Expected order to be descending") } // fetch all records { - mr, err := svc.Find(module.ID, "", "name asc, email desc", 0, 20) + + mr, f, err := svc.Find(types.RecordFilter{ModuleID: module1.ID, Sort: "name asc, email desc"}) test.Assert(t, err == nil, "Error when retrieving records: %+v", err) - test.Assert(t, len(mr.Records) == 2, "Expected two record, got %d", len(mr.Records)) - test.Assert(t, mr.Meta.Count == 2, "Expected Meta.Count == 2, got %d", mr.Meta.Count) - test.Assert(t, mr.Meta.Sort == "name asc, email desc", "Expected Meta.Sort == 'name asc, email desc' '%s'", mr.Meta.Sort) - test.Assert(t, mr.Records[0].ModuleID == m1.ModuleID, "Expected record module to match, %d != %d", m1.ModuleID, mr.Records[0].ModuleID) + test.Assert(t, len(mr) == 2, "Expected two record, got %d", len(mr)) + test.Assert(t, f.Count == 2, "Expected Meta.Count == 2, got %d", f.Count) + test.Assert(t, f.Sort == "name asc, email desc", "Expected Meta.Sort == 'name asc, email desc' '%s'", f.Sort) + test.Assert(t, mr[0].ModuleID == m1.ModuleID, "Expected record module1 to match, %d != %d", m1.ModuleID, mr[0].ModuleID) // @todo sort is not stable - // test.Assert(t, mr.Records[0].ID > mr.Records[1].ID, "Expected order to be ascending") + // test.Assert(t, mr[0].ID > mr[1].ID, "Expected order to be ascending") } // fetch all records { - mr, err := svc.Find(module.ID, "", "created_at desc", 0, 20) + mr, f, err := svc.Find(types.RecordFilter{ModuleID: module1.ID, Sort: "created_at desc"}) test.Assert(t, err == nil, "Error when retrieving records: %+v", err) - test.Assert(t, len(mr.Records) == 2, "Expected two record, got %d", len(mr.Records)) - test.Assert(t, mr.Meta.Count == 2, "Expected Meta.Count == 2, got %d", mr.Meta.Count) - test.Assert(t, mr.Meta.Sort == "created_at desc", "Expected Meta.Sort == created_at desc, got '%s'", mr.Meta.Sort) - test.Assert(t, mr.Records[0].ModuleID == m1.ModuleID, "Expected record module to match, %d != %d", m1.ModuleID, mr.Records[0].ModuleID) + test.Assert(t, len(mr) == 2, "Expected two record, got %d", len(mr)) + test.Assert(t, f.Count == 2, "Expected Meta.Count == 2, got %d", f.Count) + test.Assert(t, f.Sort == "created_at desc", "Expected Meta.Sort == created_at desc, got '%s'", f.Sort) + test.Assert(t, mr[0].ModuleID == m1.ModuleID, "Expected record module1 to match, %d != %d", m1.ModuleID, mr[0].ModuleID) // @todo sort is not stable - // test.Assert(t, mr.Records[0].ID > mr.Records[1].ID, "Expected order to be ascending") + // test.Assert(t, mr[0].ID > mr[1].ID, "Expected order to be ascending") } // fetch all records by query @@ -180,37 +205,37 @@ func TestRecord(t *testing.T) { filter := "name='John Doe' AND email='john.doe@example.com'" sort := "id desc" - mr, err := svc.Find(module.ID, filter, sort, 0, 20) + mr, f, err := svc.Find(types.RecordFilter{ModuleID: module1.ID, Sort: sort, Filter: filter}) test.Assert(t, err == nil, "Error when retrieving records: %+v", err) - test.Assert(t, len(mr.Records) == 1, "Expected one record, got %d", len(mr.Records)) - test.Assert(t, mr.Meta.Count == 1, "Expected Meta.Count == 1, got %d", mr.Meta.Count) - test.Assert(t, mr.Meta.Page == 0, "Expected Meta.Page == 0, got %d", mr.Meta.Page) - test.Assert(t, mr.Meta.PerPage == 20, "Expected Meta.PerPage == 20, got %d", mr.Meta.PerPage) - test.Assert(t, mr.Meta.Filter == filter, "Expected Meta.Filter == %q, got %q", filter, mr.Meta.Filter) - test.Assert(t, mr.Meta.Sort == sort, "Expected Meta.Sort == %q, got %q", sort, mr.Meta.Sort) + test.Assert(t, len(mr) == 1, "Expected one record, got %d", len(mr)) + test.Assert(t, f.Count == 1, "Expected Meta.Count == 1, got %d", f.Count) + test.Assert(t, f.Page == 0, "Expected Meta.Page == 0, got %d", f.Page) + test.Assert(t, f.PerPage == 50, "Expected Meta.PerPage == 50, got %d", f.PerPage) + test.Assert(t, f.Filter == filter, "Expected Meta.Filter == %q, got %q", filter, f.Filter) + test.Assert(t, f.Sort == sort, "Expected Meta.Sort == %q, got %q", sort, f.Sort) } // fetch all records by query { - mr, err := svc.Find(module.ID, "name='niall'", "id asc", 0, 20) + mr, _, err := svc.Find(types.RecordFilter{ModuleID: module1.ID, Sort: "id asc", Filter: "name='niall'"}) test.Assert(t, err == nil, "Error when retrieving records: %+v", err) - test.Assert(t, len(mr.Records) == 0, "Expected no records, got %d", len(mr.Records)) + test.Assert(t, len(mr) == 0, "Expected no records, got %d", len(mr)) } // delete record { - err := svc.DeleteByID(m1.ID) + err := svc.DeleteByID(m1.NamespaceID, m1.ID) test.Assert(t, err == nil, "Error when retrieving record by id: %+v", err) - err = svc.DeleteByID(m2.ID) + err = svc.DeleteByID(m2.NamespaceID, m2.ID) test.Assert(t, err == nil, "Error when retrieving record by id: %+v", err) } // fetch all records { - mr, err := svc.Find(module.ID, "", "", 0, 20) + mr, _, err := svc.Find(types.RecordFilter{ModuleID: module1.ID}) test.Assert(t, err == nil, "Error when retrieving records: %+v", err) - test.Assert(t, len(mr.Records) == 0, "Expected no record, got %d", len(mr.Records)) + test.Assert(t, len(mr) == 0, "Expected no record, got %d", len(mr)) } } } diff --git a/compose/rest/record.go b/compose/rest/record.go index 14ce5cc95..c2c3d91fe 100644 --- a/compose/rest/record.go +++ b/compose/rest/record.go @@ -14,28 +14,52 @@ import ( var _ = errors.Wrap -type Record struct { - record service.RecordService - attachment service.AttachmentService -} +type ( + recordPayload struct { + *types.Record + + CanUpdateRecord bool `json:"canUpdateRecord"` + CanDeleteRecord bool `json:"canDeleteRecord"` + } + + recordSetPayload struct { + Filter types.RecordFilter `json:"filter"` + Set []*recordPayload `json:"set"` + } + + Record struct { + record service.RecordService + attachment service.AttachmentService + permissions service.PermissionsService + } +) func (Record) New() *Record { return &Record{ - record: service.DefaultRecord, - attachment: service.DefaultAttachment, + record: service.DefaultRecord, + attachment: service.DefaultAttachment, + permissions: service.DefaultPermissions, } } func (ctrl *Record) Report(ctx context.Context, r *request.RecordReport) (interface{}, error) { - return ctrl.record.With(ctx).Report(r.ModuleID, r.Metrics, r.Dimensions, r.Filter) + return ctrl.record.With(ctx).Report(r.NamespaceID, r.ModuleID, r.Metrics, r.Dimensions, r.Filter) } func (ctrl *Record) List(ctx context.Context, r *request.RecordList) (interface{}, error) { - return ctrl.record.With(ctx).Find(r.ModuleID, r.Filter, r.Sort, r.Page, r.PerPage) + set, filter, err := ctrl.record.With(ctx).Find(types.RecordFilter{ + NamespaceID: r.NamespaceID, + ModuleID: r.ModuleID, + Filter: r.Filter, + Sort: r.Sort, + PerPage: r.PerPage, + Page: r.Page, + }) + return ctrl.makeFilterPayload(ctx, set, filter, err) } func (ctrl *Record) Read(ctx context.Context, r *request.RecordRead) (interface{}, error) { - return ctrl.record.With(ctx).FindByID(r.RecordID) + return ctrl.record.With(ctx).FindByID(r.NamespaceID, r.RecordID) } func (ctrl *Record) Create(ctx context.Context, r *request.RecordCreate) (interface{}, error) { @@ -50,7 +74,7 @@ func (ctrl *Record) Update(ctx context.Context, r *request.RecordUpdate) (interf } func (ctrl *Record) Delete(ctx context.Context, r *request.RecordDelete) (interface{}, error) { - return resputil.OK(), ctrl.record.With(ctx).DeleteByID(r.RecordID) + return resputil.OK(), ctrl.record.With(ctx).DeleteByID(r.NamespaceID, r.RecordID) } func (ctrl *Record) Upload(ctx context.Context, r *request.RecordUpload) (interface{}, error) { @@ -73,3 +97,32 @@ func (ctrl *Record) Upload(ctx context.Context, r *request.RecordUpload) (interf return makeAttachmentPayload(ctx, a, err) } + +func (ctrl Record) makePayload(ctx context.Context, c *types.Record, err error) (*recordPayload, error) { + if err != nil || c == nil { + return nil, err + } + + perm := ctrl.permissions.With(ctx) + + return &recordPayload{ + Record: c, + + CanUpdateRecord: perm.CanUpdateRecord(c), + CanDeleteRecord: perm.CanDeleteRecord(c), + }, nil +} + +func (ctrl Record) makeFilterPayload(ctx context.Context, nn types.RecordSet, f types.RecordFilter, err error) (*recordSetPayload, error) { + if err != nil { + return nil, err + } + + modp := &recordSetPayload{Filter: f, Set: make([]*recordPayload, len(nn))} + + for i := range nn { + modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + } + + return modp, nil +} diff --git a/compose/rest/request/record.go b/compose/rest/request/record.go index ac07bdb4a..be2ec3b9e 100644 --- a/compose/rest/request/record.go +++ b/compose/rest/request/record.go @@ -95,8 +95,8 @@ var _ RequestFiller = NewRecordReport() // Record list request parameters type RecordList struct { Filter string - Page int - PerPage int + Page uint + PerPage uint Sort string NamespaceID uint64 `json:",string"` ModuleID uint64 `json:",string"` @@ -139,11 +139,11 @@ func (rReq *RecordList) Fill(r *http.Request) (err error) { } if val, ok := get["page"]; ok { - rReq.Page = parseInt(val) + rReq.Page = parseUint(val) } if val, ok := get["perPage"]; ok { - rReq.PerPage = parseInt(val) + rReq.PerPage = parseUint(val) } if val, ok := get["sort"]; ok { diff --git a/compose/types/record.go b/compose/types/record.go index 7517d1697..9ea96fd99 100644 --- a/compose/types/record.go +++ b/compose/types/record.go @@ -14,7 +14,7 @@ type ( Values RecordValueSet `json:"values,omitempty" db:"-"` - NamespaceID uint64 `json:"namespaceID,string", db:"rel_namespace"` + NamespaceID uint64 `json:"namespaceID,string" db:"rel_namespace"` OwnedBy uint64 `db:"owned_by" json:"ownedBy,string"` CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` @@ -24,6 +24,16 @@ type ( DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` DeletedBy uint64 `db:"deleted_by" json:"deletedBy,string,omitempty" ` } + + RecordFilter struct { + ModuleID uint64 `json:"moduleID,string"` + NamespaceID uint64 `json:"namespaceID,string"` + Filter string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + Sort string `json:"sort"` + Count uint `json:"count"` + } ) // UserIDs returns a slice of user IDs from all items in the set diff --git a/docs/compose/README.md b/docs/compose/README.md index 45ef5f8c5..baf226986 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -658,8 +658,8 @@ Compose records | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | filter | string | GET | Filtering condition | N/A | NO | -| page | int | GET | Page number (0 based) | N/A | NO | -| perPage | int | GET | Returned items per page (default 50) | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | | sort | string | GET | Sort field (default id desc) | N/A | NO | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | | moduleID | uint64 | PATH | Module ID | N/A | YES | From 19f6168506551d5df22da765c1739a275e446670 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Mon, 29 Apr 2019 08:45:45 +0200 Subject: [PATCH 10/14] Fix all tests broken by compose/namespace change --- .drone.yml | 6 + compose/internal/repository/namespace.go | 4 +- compose/internal/repository/page.go | 5 +- compose/internal/service/chart_test.go | 82 ++++++++++++- compose/internal/service/main_test.go | 15 +++ compose/internal/service/module.go | 5 + compose/internal/service/module_test.go | 16 ++- compose/internal/service/namespace_test.go | 93 +++++++++++++++ compose/internal/service/page.go | 119 ++++++++++++------- compose/internal/service/page_test.go | 83 +++++++------ compose/internal/service/permissions.go | 1 + compose/internal/service/permissions_test.go | 80 ------------- compose/internal/service/service.go | 4 +- compose/internal/service/trigger.go | 4 + compose/internal/service/trigger_test.go | 14 ++- compose/types/page.go | 3 +- 16 files changed, 348 insertions(+), 186 deletions(-) create mode 100644 compose/internal/service/namespace_test.go delete mode 100644 compose/internal/service/permissions_test.go diff --git a/.drone.yml b/.drone.yml index 917de60f5..3ea1411a9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -93,6 +93,10 @@ steps: CI: circleci AUTH_OIDC_ENABLED: 0 AUTH_JWT_SECRET: bRxJ37sJ6Qu4 + DB_DSN: crust:crust@tcp(crust-db:3306)/crust?collation=utf8mb4_general_ci + SYSTEM_DB_DSN: crust:crust@tcp(crust-db:3306)/crust?collation=utf8mb4_general_ci + COMPOSE_DB_DSN: crust:crust@tcp(crust-db:3306)/crust?collation=utf8mb4_general_ci + MESSAGING_DB_DSN: crust:crust@tcp(crust-db:3306)/crust?collation=utf8mb4_general_ci commands: - make mocks - make codegen @@ -103,11 +107,13 @@ steps: - go build -ldflags "-X github.com/crusttech/crust/internal/version.BuildTime=`date +%FT%T%z` -X github.com/crusttech/crust/internal/version.Version=`git describe --always --tags`" -o build/system-cli-$GOOS-$GOARCH cmd/system-cli/*.go # check all tests are able to build - wait-for-it.sh -t 60 --strict crust-db:3306 -- echo "Crust DB1 is up" + - ./build/system-cli-$GOOS-$GOARCH roles reset - gotest -v --tags="migrations" ./system/db/... - gotest -v --tags="migrations" ./compose/db/... - gotest -v --tags="migrations" ./messaging/db/... - go test -run=^$ --tags="unit integration external" ./cmd/... ./internal/... ./compose/... ./messaging/... ./system/... - gotest -failfast --coverprofile=coverage.txt -v --tags="unit integration" ./internal/... ./system/... ./compose/... ./messaging/... + - name: coverage image: plugins/codecov settings: diff --git a/compose/internal/repository/namespace.go b/compose/internal/repository/namespace.go index 11c5d870c..a8cfae8e9 100644 --- a/compose/internal/repository/namespace.go +++ b/compose/internal/repository/namespace.go @@ -116,7 +116,7 @@ func (r *namespace) Update(mod *types.Namespace) (*types.Namespace, error) { return mod, r.db().Replace(r.table(), mod) } -func (r *namespace) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM "+r.table()+" WHERE id=?", id) +func (r *namespace) DeleteByID(namespaceID uint64) error { + _, err := r.db().Exec("UPDATE "+r.table()+" SET deleted_at = NOW() WHERE id = ?", namespaceID) return err } diff --git a/compose/internal/repository/page.go b/compose/internal/repository/page.go index 341f99a0a..02d8b0bde 100644 --- a/compose/internal/repository/page.go +++ b/compose/internal/repository/page.go @@ -15,7 +15,7 @@ type ( With(ctx context.Context, db *factory.DB) PageRepository FindByID(namespaceID, pageID uint64) (*types.Page, error) - FindByModuleID(namespaceID, pageID uint64) (*types.Page, error) + FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) Create(mod *types.Page) (*types.Page, error) @@ -107,6 +107,8 @@ func (r page) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilt if filter.ParentID > 0 { query = query.Where("self_id = ?", filter.ParentID) + } else if filter.Root { + query = query.Where("self_id = 0") } if f.Query != "" { @@ -171,6 +173,7 @@ func (r page) Create(mod *types.Page) (*types.Page, error) { func (r page) Update(mod *types.Page) (*types.Page, error) { now := time.Now() mod.UpdatedAt = &now + return mod, r.db().Replace(r.table(), mod) } diff --git a/compose/internal/service/chart_test.go b/compose/internal/service/chart_test.go index 66bec541a..ddd93eee4 100644 --- a/compose/internal/service/chart_test.go +++ b/compose/internal/service/chart_test.go @@ -1,11 +1,87 @@ -// +build unit +// +build integration package service import ( + "context" "testing" + + "github.com/pkg/errors" + + "github.com/crusttech/crust/compose/types" + "github.com/crusttech/crust/internal/auth" + "github.com/crusttech/crust/internal/test" + systemTypes "github.com/crusttech/crust/system/types" ) -func TestModuleGraph(t *testing.T) { - // repository := Module().With(context.Background()) +func TestChart(t *testing.T) { + ctx := context.WithValue(context.Background(), "testing", true) + + user := &systemTypes.User{ + ID: 1337, + Name: "John Crm Doe", + Username: "johndoe", + } + + // Set Identity (required for permission checks). + ctx = auth.SetIdentityToContext(ctx, user) + + ns1, _ := createTestNamespaces(ctx, t) + + svc := Chart().With(ctx) + + // the chart object we're working with + chart := &types.Chart{ + NamespaceID: ns1.ID, + Name: "Test", + } + + { + { + m, err := svc.Update(chart) + test.Assert(t, m == nil, "Expected empty return for invalid update, got %#v", m) + test.Assert(t, err != nil, "Expected error when updating invalid content") + } + + // create chart + m, err := svc.Create(chart) + test.Assert(t, err == nil, "Error when creating chart: %+v", err) + test.Assert(t, m.ID > 0, "Expected auto generated ID") + + { + _, err := svc.Create(chart) + test.Assert(t, err == nil, "Unexpected error when creating chart, %+v", err) + } + + // fetch created chart + { + ms, err := svc.FindByID(m.NamespaceID, m.ID) + test.Assert(t, err == nil, "Error when retrieving chart by id: %+v", err) + test.Assert(t, ms.ID == m.ID, "Expected ID from database to match, %+v", errors.Errorf("%d != %d", m.ID, ms.ID)) + test.Assert(t, ms.Name == m.Name, "Expected Name from database to match, %+v", errors.Errorf("%s != %s", m.Name, ms.Name)) + } + + // update created chart + { + m.UpdatedAt = nil + m.Name = "Updated test" + _, err := svc.Update(m) + test.Assert(t, err == nil, "Error when updating chart, %+v", err) + } + + // re-fetch chart + { + ms, err := svc.FindByID(m.NamespaceID, m.ID) + test.Assert(t, err == nil, "Error when retrieving chart by id: %+v", err) + test.Assert(t, ms.ID == m.ID, "re-fetch: Expected ID from database to match, %d != %d", m.ID, ms.ID) + test.Assert(t, ms.Name == m.Name, "Expected Name from database to match, %s != %s", m.Name, ms.Name) + } + + // delete chart + { + err := svc.DeleteByID(m.NamespaceID, m.ID) + test.Assert(t, err == nil, "Error when deleting chart by id: %+v", err) + } + } + } diff --git a/compose/internal/service/main_test.go b/compose/internal/service/main_test.go index d71f79a2f..ebecd84d9 100644 --- a/compose/internal/service/main_test.go +++ b/compose/internal/service/main_test.go @@ -3,6 +3,7 @@ package service import ( + "context" "log" "os" "testing" @@ -11,6 +12,8 @@ import ( "github.com/titpetric/factory" composeMigrate "github.com/crusttech/crust/compose/db" + "github.com/crusttech/crust/compose/types" + "github.com/crusttech/crust/internal/test" systemMigrate "github.com/crusttech/crust/system/db" systemService "github.com/crusttech/crust/system/service" ) @@ -56,3 +59,15 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } + +func createTestNamespaces(ctx context.Context, t *testing.T) (ns1 *types.Namespace, ns2 *types.Namespace) { + var err error + + ns1, err = Namespace().With(ctx).Create(&types.Namespace{Enabled: true, Name: "TestNamespace"}) + test.Assert(t, err == nil, "Error when creating namespace: %+v", err) + + ns2, err = Namespace().With(ctx).Create(&types.Namespace{Enabled: true, Name: "TestNamespace"}) + test.Assert(t, err == nil, "Error when creating namespace: %+v", err) + + return ns1, ns2 +} diff --git a/compose/internal/service/module.go b/compose/internal/service/module.go index ed723ca76..c45d30bce 100644 --- a/compose/internal/service/module.go +++ b/compose/internal/service/module.go @@ -100,6 +100,11 @@ func (svc *module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types } func (svc *module) Create(mod *types.Module) (*types.Module, error) { + + if mod.NamespaceID == 0 { + return nil, ErrNamespaceRequired.withStack() + } + if !svc.prmSvc.CanCreateModule(crmNamespace()) { return nil, ErrNoCreatePermissions.withStack() } diff --git a/compose/internal/service/module_test.go b/compose/internal/service/module_test.go index 55d8ab445..d8fd5eaf3 100644 --- a/compose/internal/service/module_test.go +++ b/compose/internal/service/module_test.go @@ -24,11 +24,14 @@ func TestModule(t *testing.T) { // Set Identity (required for permission checks). ctx = auth.SetIdentityToContext(ctx, user) + ns1, _ := createTestNamespaces(ctx, t) + svc := Module().With(ctx) // the module object we're working with module := &types.Module{ - Name: "Test", + NamespaceID: ns1.ID, + Name: "Test", Fields: types.ModuleFieldSet{ &types.ModuleField{ Name: "name", @@ -65,7 +68,7 @@ func TestModule(t *testing.T) { // fetch created module { - ms, err := svc.FindByID(m.ID) + ms, err := svc.FindByID(m.NamespaceID, m.ID) test.Assert(t, err == nil, "Error when retrieving module by id: %+v", err) test.Assert(t, ms.ID == m.ID, "Expected ID from database to match, %d != %d", m.ID, ms.ID) test.Assert(t, ms.Name == m.Name, "Expected Name from database to match, %s != %s", m.Name, ms.Name) @@ -75,6 +78,7 @@ func TestModule(t *testing.T) { // update created module { m.Name = "Updated test" + m.UpdatedAt = nil _, err := svc.Update(m) test.Assert(t, err == nil, "Error when updating module, %+v", err) } @@ -88,7 +92,7 @@ func TestModule(t *testing.T) { // re-fetch module { - ms, err := svc.FindByID(m.ID) + ms, err := svc.FindByID(m.NamespaceID, m.ID) test.Assert(t, err == nil, "Error when retrieving module by id: %+v", err) test.Assert(t, ms.ID == m.ID, "Expected ID from database to match, %d != %d", m.ID, ms.ID) test.Assert(t, ms.Name == m.Name, "Expected Name from database to match, %s != %s", m.Name, ms.Name) @@ -96,7 +100,7 @@ func TestModule(t *testing.T) { // fetch all modules { - ms, err := svc.Find() + ms, _, err := svc.Find(types.ModuleFilter{}) test.Assert(t, err == nil, "Error when retrieving modules: %+v", err) test.Assert(t, len(ms) >= 1, "Expected at least one module, got %d", len(ms)) prevModuleCount = len(ms) @@ -104,13 +108,13 @@ func TestModule(t *testing.T) { // re-fetch module { - err := svc.DeleteByID(m.ID) + err := svc.DeleteByID(m.NamespaceID, m.ID) test.Assert(t, err == nil, "Error when deleting module by id: %+v", err) } // fetch all modules { - ms, err := svc.Find() + ms, _, err := svc.Find(types.ModuleFilter{}) test.Assert(t, err == nil, "Error when retrieving modules: %+v", err) test.Assert(t, len(ms) < prevModuleCount, "Expected modules count to decrease after deletion, %d < %d", len(ms), prevModuleCount) } diff --git a/compose/internal/service/namespace_test.go b/compose/internal/service/namespace_test.go new file mode 100644 index 000000000..ab5075747 --- /dev/null +++ b/compose/internal/service/namespace_test.go @@ -0,0 +1,93 @@ +// +build integration + +package service + +import ( + "context" + "testing" + + "github.com/crusttech/crust/compose/types" + "github.com/crusttech/crust/internal/auth" + "github.com/crusttech/crust/internal/test" + systemTypes "github.com/crusttech/crust/system/types" +) + +func TestNamespace(t *testing.T) { + ctx := context.WithValue(context.Background(), "testing", true) + + user := &systemTypes.User{ + ID: 1337, + Name: "John Crm Doe", + Username: "johndoe", + } + + // Set Identity (required for permission checks). + ctx = auth.SetIdentityToContext(ctx, user) + + svc := Namespace().With(ctx) + + // the namespace object we're working with + namespace := &types.Namespace{ + Name: "Test", + } + + prevNamespaceCount := uint(0) + + { + { + m, err := svc.Update(namespace) + test.Assert(t, m == nil, "Expected empty return for invalid update, got %#v", m) + test.Assert(t, err != nil, "Expected error when updating invalid content") + } + + // create namespace + m, err := svc.Create(namespace) + test.Assert(t, err == nil, "Error when creating namespace: %+v", err) + test.Assert(t, m.ID > 0, "Expected auto generated ID") + + // fetch created namespace + { + ms, err := svc.FindByID(m.ID) + test.Assert(t, err == nil, "Error when retrieving namespace by id: %+v", err) + test.Assert(t, ms.ID == m.ID, "Expected ID from database to match, %d != %d", m.ID, ms.ID) + test.Assert(t, ms.Name == m.Name, "Expected Name from database to match, %s != %s", m.Name, ms.Name) + } + + // update created namespace + { + m.Name = "Updated test" + m.UpdatedAt = nil + _, err := svc.Update(m) + test.Assert(t, err == nil, "Error when updating namespace, %+v", err) + } + + // re-fetch namespace + { + ms, err := svc.FindByID(m.ID) + test.Assert(t, err == nil, "Error when retrieving namespace by id: %+v", err) + test.Assert(t, ms.ID == m.ID, "Expected ID from database to match, %d != %d", m.ID, ms.ID) + test.Assert(t, ms.Name == m.Name, "Expected Name from database to match, %s != %s", m.Name, ms.Name) + } + + // fetch all namespaces + { + _, f, err := svc.Find(types.NamespaceFilter{}) + test.Assert(t, err == nil, "Error when retrieving namespaces: %+v", err) + test.Assert(t, f.Count > 0, "Expected at least one namespace, got %d", f.Count) + prevNamespaceCount = f.Count + } + + // re-fetch namespace + { + err := svc.DeleteByID(m.ID) + test.Assert(t, err == nil, "Error when deleting namespace by id: %+v", err) + } + + // fetch all namespaces + { + _, f, err := svc.Find(types.NamespaceFilter{}) + test.Assert(t, err == nil, "Error when retrieving namespaces: %+v", err) + test.Assert(t, f.Count < prevNamespaceCount, "Expected namespaces count to decrease after deletion, %d < %d", f.Count, prevNamespaceCount) + } + } +} diff --git a/compose/internal/service/page.go b/compose/internal/service/page.go index 579d84094..d13d021e3 100644 --- a/compose/internal/service/page.go +++ b/compose/internal/service/page.go @@ -38,6 +38,10 @@ type ( } ) +const ( + ErrModulePageExists serviceError = "ModulePageExists" +) + func Page() PageService { return (&page{ prmSvc: DefaultPermissions, @@ -76,17 +80,32 @@ func (svc *page) checkPermissions(p *types.Page, err error) (*types.Page, error) } func (svc *page) FindBySelfID(namespaceID, parentID uint64) (pp types.PageSet, f types.PageFilter, err error) { + if namespaceID == 0 { + return nil, f, ErrNamespaceRequired.withStack() + } + return svc.filterPageSetByPermission(svc.pageRepo.Find(types.PageFilter{ NamespaceID: namespaceID, ParentID: parentID, + + // This will enable parentID=0 query + Root: true, })) } func (svc *page) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) { + if filter.NamespaceID == 0 { + return nil, f, ErrNamespaceRequired.withStack() + } + return svc.filterPageSetByPermission(svc.pageRepo.Find(filter)) } func (svc *page) Tree(namespaceID uint64) (pages types.PageSet, err error) { + if namespaceID == 0 { + return nil, ErrNamespaceRequired.withStack() + } + var ( tree types.PageSet filter = types.PageFilter{ @@ -140,58 +159,70 @@ func (svc *page) Reorder(namespaceID, selfID uint64, pageIDs []uint64) error { return svc.pageRepo.Reorder(namespaceID, selfID, pageIDs) } -func (svc *page) Create(page *types.Page) (p *types.Page, err error) { - validate := func() error { - if !svc.prmSvc.CanCreatePage(crmNamespace()) { - return errors.New("not allowed to create this page") - } +func (svc *page) Create(mod *types.Page) (p *types.Page, err error) { + mod.ID = 0 - if page.ModuleID > 0 { - if p, err = svc.pageRepo.FindByModuleID(page.NamespaceID, page.ModuleID); err != nil { - return err - } else if p.ID > 0 { - return errors.New("Page for module already exists") - } - } - return nil + if mod.NamespaceID == 0 { + return nil, ErrNamespaceRequired.withStack() } - if err := validate(); err != nil { - return nil, err + + if !svc.prmSvc.CanCreatePage(crmNamespace()) { + return nil, ErrNoCreatePermissions.withStack() } - return p, svc.db.Transaction(func() (err error) { - p, err = svc.pageRepo.Create(page) + + if err = svc.checkModulePage(mod); err != nil { return - }) + } + + p, err = svc.pageRepo.Create(mod) + return } -func (svc *page) Update(page *types.Page) (p *types.Page, err error) { - validate := func() error { - if page.ID == 0 { - return errors.New("Error when saving page, invalid ID") - } else if p, err = svc.pageRepo.FindByID(page.NamespaceID, page.ID); err != nil { - return errors.Wrap(err, "Error while loading page for update") - } else { - if !svc.prmSvc.CanUpdatePage(p) { - return errors.New("not allowed to update this page") - } - } +func (svc *page) Update(mod *types.Page) (p *types.Page, err error) { + if mod.ID == 0 { + return nil, ErrInvalidID.withStack() + } - if page.ModuleID > 0 { - if p, err = svc.pageRepo.FindByModuleID(page.NamespaceID, page.ModuleID); err != nil { - return err - } else if p.ID > 0 && page.ID != p.ID { - return errors.New("Page for module already exists") - } - } - return nil - } - if err := validate(); err != nil { - return nil, err - } - return p, svc.db.Transaction(func() (err error) { - p, err = svc.pageRepo.Update(page) + if p, err = svc.pageRepo.FindByID(mod.NamespaceID, mod.ID); err != nil { return - }) + } + + if isStale(mod.UpdatedAt, p.UpdatedAt, p.CreatedAt) { + return nil, ErrStaleData.withStack() + } + + if !svc.prmSvc.CanUpdatePage(p) { + return nil, ErrNoUpdatePermissions.withStack() + } + + if err = svc.checkModulePage(mod); err != nil { + return + } + + p.ModuleID = mod.ModuleID + p.SelfID = mod.SelfID + p.Blocks = mod.Blocks + p.Title = mod.Title + p.Description = mod.Description + p.Visible = mod.Visible + p.Weight = mod.Weight + + p, err = svc.pageRepo.Update(p) + return +} + +func (svc page) checkModulePage(mod *types.Page) error { + if mod.ModuleID > 0 { + if p, err := svc.pageRepo.FindByModuleID(mod.NamespaceID, mod.ModuleID); err != nil { + if err.Error() != repository.ErrPageNotFound.Error() { + return err + } + } else if p.ID > 0 && mod.ID != p.ID { + return ErrModulePageExists + } + } + + return nil } func (svc *page) DeleteByID(namespaceID, pageID uint64) error { diff --git a/compose/internal/service/page_test.go b/compose/internal/service/page_test.go index 0547b0033..d3f2b66d8 100644 --- a/compose/internal/service/page_test.go +++ b/compose/internal/service/page_test.go @@ -27,12 +27,16 @@ func TestPage(t *testing.T) { // Set Identity (required for permission checks). ctx = auth.SetIdentityToContext(ctx, user) - repository := Page().With(ctx) + ns1, _ := createTestNamespaces(ctx, t) + + svc := Page().With(ctx) // the page object we're working with + var err error page := &types.Page{ - Title: "Test", - ModuleID: 123, + NamespaceID: ns1.ID, + Title: "Test", + ModuleID: 123, } (&page.Blocks).Scan([]byte("[]")) @@ -40,104 +44,99 @@ func TestPage(t *testing.T) { { { - m, err := repository.Update(page) + m, err := svc.Update(page) test.Assert(t, m == nil, "Expected empty return for invalid update, got %#v", m) test.Assert(t, err != nil, "Expected error when updating invalid content") } // create page - m, err := repository.Create(page) + page, err = svc.Create(page) test.Assert(t, err == nil, "Error when creating page: %+v", err) - test.Assert(t, m.ID > 0, "Expected auto generated ID") + test.Assert(t, page.ID > 0, "Expected auto generated ID") - page.SelfID = m.ID + var firstPageID = page.ID + + page.SelfID = page.ID { - _, err := repository.Create(page) + _, err = svc.Create(page) test.Assert(t, err != nil, "%+v", errors.Errorf("Expected error when creating duplicate moduleID page")) } + page.ModuleID = 0 + { - page.ModuleID = 0 - _, err := repository.Create(page) + _, err = svc.Create(page) test.Assert(t, err == nil, "Unexpected error when creating page, %+v", err) } { - _, err := repository.Create(page) + _, err = svc.Create(page) test.Assert(t, err == nil, "Unexpected error when creating page, %+v", err) } // fetch created page { - ms, err := repository.FindByID(m.ID) + p, err := svc.FindByID(page.NamespaceID, page.ID) test.Assert(t, err == nil, "Error when retrieving page by id: %+v", err) - test.Assert(t, ms.ID == m.ID, "Expected ID from database to match, %+v", errors.Errorf("%d != %d", m.ID, ms.ID)) - test.Assert(t, ms.Title == m.Title, "Expected Title from database to match, %+v", errors.Errorf("%s != %s", m.Title, ms.Title)) + test.Assert(t, p.ID == page.ID, "Expected ID from database to match, %+v", errors.Errorf("%d != %d", page.ID, p.ID)) + test.Assert(t, p.Title == page.Title, "Expected Title from database to match, %+v", errors.Errorf("%s != %s", page.Title, p.Title)) } // update created page { - m.Title = "Updated test" - _, err := repository.Update(m) + page.Title = "Updated test" + page.UpdatedAt = nil + _, err := svc.Update(page) test.Assert(t, err == nil, "Error when updating page, %+v", err) } // re-fetch page { - ms, err := repository.FindByID(m.ID) + p, err := svc.FindByID(page.NamespaceID, page.ID) test.Assert(t, err == nil, "Error when retrieving page by id: %+v", err) - test.Assert(t, ms.ID == m.ID, "re-fetch: Expected ID from database to match, %d != %d", m.ID, ms.ID) - test.Assert(t, ms.Title == m.Title, "Expected Title from database to match, %s != %s", m.Title, ms.Title) - } - - // re-fetch page with moduleID - { - ms, err := repository.FindByModuleID(m.ModuleID) - test.Assert(t, err == nil, "Error when retrieving page by id: %+v", err) - test.Assert(t, ms.ID == m.ID, "fetch-module: Expected ID from database to match, %d != %d", m.ID, ms.ID) - test.Assert(t, ms.Title == m.Title, "Expected Title from database to match, %s != %s", m.Title, ms.Title) + test.Assert(t, p.ID == page.ID, "re-fetch: Expected ID from database to match, %d != %d", page.ID, p.ID) + test.Assert(t, p.Title == page.Title, "Expected Title from database to match, %s != %s", page.Title, p.Title) } // fetch all pages { - ms, err := repository.FindBySelfID(0) + p, _, err := svc.FindBySelfID(page.NamespaceID, 0) test.Assert(t, err == nil, "Error when retrieving pages: %+v", err) - test.Assert(t, len(ms) >= 1, "Expected at least one page, got %d", len(ms)) - prevPageCount = len(ms) + test.Assert(t, len(p) >= 1, "Expected at least one page, got %d", len(p)) + prevPageCount = len(p) } // fetch all pages { - ms, err := repository.FindBySelfID(m.ID) + p, _, err := svc.FindBySelfID(page.NamespaceID, firstPageID) test.Assert(t, err == nil, "Error when retrieving pages: %+v", err) - test.Assert(t, len(ms) == 2, "Expected two pages with selfID=%d, got %v", m.ID, spew.Sdump(ms)) - prevPageCount = len(ms) + test.Assert(t, len(p) == 2, "Expected 2 pages with selfID=%d, got %v", page.ID, spew.Sdump(p)) + prevPageCount = len(p) - parent := m.ID - ids := []uint64{ms[0].ID, ms[1].ID} + ids := []uint64{p[0].ID, p[1].ID} { - err := repository.Reorder(parent, ids) + err := svc.Reorder(page.NamespaceID, firstPageID, ids) test.Assert(t, err == nil, "Error when reordering pages: %+v", err) - ms, err = repository.FindBySelfID(m.ID) + p, _, err = svc.FindBySelfID(page.NamespaceID, firstPageID) test.Assert(t, err == nil, "Error when retrieving pages: %+v", err) - test.Assert(t, len(ms) == 2, "Expected two pages with selfID=%d, got %v", m.ID, spew.Sdump(ms)) - test.Assert(t, ms[0].Weight < ms[1].Weight, "Expected ascending order, %+v", errors.Errorf("%d < %d", ms[0].Weight, ms[1].Weight)) + test.Assert(t, len(p) == 2, "Expected 2 pages with selfID=%d, got %v", page.ID, spew.Sdump(p)) + test.Assert(t, p[0].Weight < p[1].Weight, "Expected ascending order, %+v", errors.Errorf("%d < %d", p[0].Weight, p[1].Weight)) } } // re-fetch page { - err := repository.DeleteByID(m.ID) + err := svc.DeleteByID(page.NamespaceID, page.ID) test.Assert(t, err == nil, "Error when deleting page by id: %+v", err) } // fetch all pages { - ms, err := repository.FindBySelfID(0) + p, _, err := svc.FindBySelfID(page.NamespaceID, 0) test.Assert(t, err == nil, "Error when retrieving pages: %+v", err) - test.Assert(t, len(ms) < prevPageCount, "Expected pages count to decrease after deletion, %d < %d", len(ms), prevPageCount) + test.Assert(t, len(p) < prevPageCount, "Expected pages count to decrease after deletion, %d < %d", len(p), prevPageCount) } } diff --git a/compose/internal/service/permissions.go b/compose/internal/service/permissions.go index 46a5a10ae..c5c8ea560 100644 --- a/compose/internal/service/permissions.go +++ b/compose/internal/service/permissions.go @@ -27,6 +27,7 @@ type ( Effective() (ee []effectivePermission, err error) CanAccess() bool + CanGrant() bool CanCreateNamespace() bool CanReadNamespace(r permissionResource) bool CanUpdateNamespace(r permissionResource) bool diff --git a/compose/internal/service/permissions_test.go b/compose/internal/service/permissions_test.go deleted file mode 100644 index 05b1c5150..000000000 --- a/compose/internal/service/permissions_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// +build integration - -package service - -import ( - "context" - "testing" - - "github.com/crusttech/crust/compose/internal/repository" - "github.com/crusttech/crust/compose/types" - "github.com/crusttech/crust/internal/auth" - "github.com/crusttech/crust/internal/rules" - . "github.com/crusttech/crust/internal/test" - systemService "github.com/crusttech/crust/system/service" - systemTypes "github.com/crusttech/crust/system/types" -) - -func TestPermissions(t *testing.T) { - var err error - ctx := context.WithValue(context.Background(), "testing", true) - { - user := &systemTypes.User{ID: 1337} - ctx = auth.SetIdentityToContext(ctx, auth.NewIdentity(user.Identity())) - } - - // Create user with role and add it to context. - userSvc := systemService.TestUser(t, ctx) - user := &systemTypes.User{ - Name: "John Crm Doe", - Username: "johndoe", - } - - _, err = userSvc.Create(user) - NoError(t, err, "expected no error creating user, got %+v", err) - - ctx = auth.SetIdentityToContext(ctx, user) - - roleSvc := systemService.TestRole(t, ctx) - role := &systemTypes.Role{ - Name: "Test role v1", - } - role, err = roleSvc.Create(role) - NoError(t, err, "expected no error creating role, got %+v", err) - - err = roleSvc.MemberAdd(role.ID, user.ID) - NoError(t, err, "expected no error adding user to role, got %+v", err) - - // Insert `grant` permission for `compose`. - { - db := repository.DB(ctx) - resources := rules.NewResources(ctx, db) - - list := []rules.Rule{ - rules.Rule{Resource: types.PermissionResource, Operation: "grant", Value: rules.Allow}, - } - - err := resources.Grant(role.ID, list) - NoError(t, err, "expected no error, got %+v", err) - } - - // Generate services. - cleanContext := auth.SetIdentityToContext(context.Background(), user) - permissionsSvc := Permissions().With(cleanContext) - systemRulesSvc := systemService.TestRules(t, ctx) - - // Test `access` to compose service. - ret := permissionsSvc.CanAccess() - Assert(t, ret == false, "expected CanAccess == false, got %+v", ret) - - // Add `access` to compose service. - list := []rules.Rule{ - rules.Rule{Resource: types.PermissionResource, Operation: "access", Value: rules.Allow}, - } - _, err = systemRulesSvc.Update(role.ID, list) - NoError(t, err, "expected no error, got %+v", err) - - // Test `access` to compose service. - ret = permissionsSvc.CanAccess() - Assert(t, ret == true, "expected CanAccess == true, got %+v", ret) -} diff --git a/compose/internal/service/service.go b/compose/internal/service/service.go index a6657f45c..95d25417e 100644 --- a/compose/internal/service/service.go +++ b/compose/internal/service/service.go @@ -46,8 +46,8 @@ func Init() error { // Data is stale when new date does not match updatedAt or createdAt (before first update) func isStale(new *time.Time, updatedAt *time.Time, createdAt time.Time) bool { if new == nil { - // Change to true to require stale-data-check. - return true + // Change to true for stale-data-check + return false } if updatedAt != nil { diff --git a/compose/internal/service/trigger.go b/compose/internal/service/trigger.go index fba68b15f..322de20d5 100644 --- a/compose/internal/service/trigger.go +++ b/compose/internal/service/trigger.go @@ -77,6 +77,10 @@ func (svc trigger) Find(filter types.TriggerFilter) (set types.TriggerSet, f typ } func (svc trigger) Create(mod *types.Trigger) (c *types.Trigger, err error) { + if mod.NamespaceID == 0 { + return nil, ErrNamespaceRequired.withStack() + } + if !svc.prmSvc.CanCreateTrigger(crmNamespace()) { return nil, ErrNoCreatePermissions.withStack() } diff --git a/compose/internal/service/trigger_test.go b/compose/internal/service/trigger_test.go index 917113c1e..267a62aa0 100644 --- a/compose/internal/service/trigger_test.go +++ b/compose/internal/service/trigger_test.go @@ -26,12 +26,15 @@ func TestTrigger(t *testing.T) { // Set Identity (required for permission checks). ctx = auth.SetIdentityToContext(ctx, user) + ns1, _ := createTestNamespaces(ctx, t) + svc := Trigger().With(ctx) // the trigger object we're working with trigger := &types.Trigger{ - Name: "Test", - ModuleID: 123, + NamespaceID: ns1.ID, + Name: "Test", + ModuleID: 123, } { @@ -53,7 +56,7 @@ func TestTrigger(t *testing.T) { // fetch created trigger { - ms, err := svc.FindByID(m.ID) + ms, err := svc.FindByID(m.NamespaceID, m.ID) test.Assert(t, err == nil, "Error when retrieving trigger by id: %+v", err) test.Assert(t, ms.ID == m.ID, "Expected ID from database to match, %+v", errors.Errorf("%d != %d", m.ID, ms.ID)) test.Assert(t, ms.Name == m.Name, "Expected Name from database to match, %+v", errors.Errorf("%s != %s", m.Name, ms.Name)) @@ -61,6 +64,7 @@ func TestTrigger(t *testing.T) { // update created trigger { + m.UpdatedAt = nil m.Name = "Updated test" _, err := svc.Update(m) test.Assert(t, err == nil, "Error when updating trigger, %+v", err) @@ -68,7 +72,7 @@ func TestTrigger(t *testing.T) { // re-fetch trigger { - ms, err := svc.FindByID(m.ID) + ms, err := svc.FindByID(m.NamespaceID, m.ID) test.Assert(t, err == nil, "Error when retrieving trigger by id: %+v", err) test.Assert(t, ms.ID == m.ID, "re-fetch: Expected ID from database to match, %d != %d", m.ID, ms.ID) test.Assert(t, ms.Name == m.Name, "Expected Name from database to match, %s != %s", m.Name, ms.Name) @@ -76,7 +80,7 @@ func TestTrigger(t *testing.T) { // delete trigger { - err := svc.DeleteByID(m.ID) + err := svc.DeleteByID(m.NamespaceID, m.ID) test.Assert(t, err == nil, "Error when deleting trigger by id: %+v", err) } } diff --git a/compose/types/page.go b/compose/types/page.go index 0622e49e1..b65d407c9 100644 --- a/compose/types/page.go +++ b/compose/types/page.go @@ -47,7 +47,8 @@ type ( PageFilter struct { NamespaceID uint64 `json:"namespaceID,string"` - ParentID uint64 `json:"paentID,string"` + ParentID uint64 `json:"parentID,string,omitempty"` + Root bool `json:"root,omitempty"` Query string `json:"query"` Page uint `json:"page"` PerPage uint `json:"perPage"` From 01b3cc529bdcbfaefd689219cc0da4b92c350ec0 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Mon, 29 Apr 2019 18:48:01 +0200 Subject: [PATCH 11/14] Add missing namespaceID filter params --- compose/rest/chart.go | 2 ++ compose/rest/module.go | 7 ++++--- compose/rest/trigger.go | 7 ++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/compose/rest/chart.go b/compose/rest/chart.go index c351a2d13..683670867 100644 --- a/compose/rest/chart.go +++ b/compose/rest/chart.go @@ -42,6 +42,8 @@ func (Chart) New() *Chart { func (ctrl Chart) List(ctx context.Context, r *request.ChartList) (interface{}, error) { f := types.ChartFilter{ + NamespaceID: r.NamespaceID, + Query: r.Query, PerPage: r.PerPage, Page: r.Page, diff --git a/compose/rest/module.go b/compose/rest/module.go index 450b0fec5..f1a5435cb 100644 --- a/compose/rest/module.go +++ b/compose/rest/module.go @@ -44,9 +44,10 @@ func (Module) New() *Module { func (ctrl *Module) List(ctx context.Context, r *request.ModuleList) (interface{}, error) { f := types.ModuleFilter{ - Query: r.Query, - PerPage: r.PerPage, - Page: r.Page, + NamespaceID: r.NamespaceID, + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, } set, filter, err := ctrl.module.With(ctx).Find(f) diff --git a/compose/rest/trigger.go b/compose/rest/trigger.go index 7921ba2a5..78392d03d 100644 --- a/compose/rest/trigger.go +++ b/compose/rest/trigger.go @@ -38,9 +38,10 @@ func (Trigger) New() *Trigger { func (ctrl Trigger) List(ctx context.Context, r *request.TriggerList) (interface{}, error) { f := types.TriggerFilter{ - Query: r.Query, - PerPage: r.PerPage, - Page: r.Page, + NamespaceID: r.NamespaceID, + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, } set, filter, err := ctrl.trigger.With(ctx).Find(f) From a9358610f545eb1e05b69b5594f54173b4c642fb Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 30 Apr 2019 13:33:25 +0200 Subject: [PATCH 12/14] Make sign & userID params for attachment endpoints non-required --- api/compose/spec.json | 4 ++-- api/compose/spec/attachment.json | 4 ++-- docs/compose/README.md | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/api/compose/spec.json b/api/compose/spec.json index 7fd06455f..7bdc718ab 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -1197,13 +1197,13 @@ { "type": "string", "name": "sign", - "required": true, + "required": false, "title": "Signature" }, { "type": "uint64", "name": "userID", - "required": true, + "required": false, "title": "User ID" } ] diff --git a/api/compose/spec/attachment.json b/api/compose/spec/attachment.json index 6f4873af2..6b4f62a6b 100644 --- a/api/compose/spec/attachment.json +++ b/api/compose/spec/attachment.json @@ -6,13 +6,13 @@ "get": [ { "name": "sign", - "required": true, + "required": false, "title": "Signature", "type": "string" }, { "name": "userID", - "required": true, + "required": false, "title": "User ID", "type": "uint64" } diff --git a/docs/compose/README.md b/docs/compose/README.md index baf226986..9da4d4e57 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -26,8 +26,8 @@ | fieldName | string | GET | Filter attachments by field name | N/A | NO | | page | uint | GET | Page number (0 based) | N/A | NO | | perPage | uint | GET | Returned items per page (default 50) | N/A | NO | -| sign | string | GET | Signature | N/A | YES | -| userID | uint64 | GET | User ID | N/A | YES | +| sign | string | GET | Signature | N/A | NO | +| userID | uint64 | GET | User ID | N/A | NO | | kind | string | PATH | Attachment kind | N/A | YES | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | @@ -46,8 +46,8 @@ | attachmentID | uint64 | PATH | Attachment ID | N/A | YES | | kind | string | PATH | Attachment kind | N/A | YES | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | -| sign | string | GET | Signature | N/A | YES | -| userID | uint64 | GET | User ID | N/A | YES | +| sign | string | GET | Signature | N/A | NO | +| userID | uint64 | GET | User ID | N/A | NO | ## Delete attachment @@ -64,8 +64,8 @@ | attachmentID | uint64 | PATH | Attachment ID | N/A | YES | | kind | string | PATH | Attachment kind | N/A | YES | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | -| sign | string | GET | Signature | N/A | YES | -| userID | uint64 | GET | User ID | N/A | YES | +| sign | string | GET | Signature | N/A | NO | +| userID | uint64 | GET | User ID | N/A | NO | ## Serves attached file @@ -80,8 +80,8 @@ | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | download | bool | GET | Force file download | N/A | NO | -| sign | string | GET | Signature | N/A | YES | -| userID | uint64 | GET | User ID | N/A | YES | +| sign | string | GET | Signature | N/A | NO | +| userID | uint64 | GET | User ID | N/A | NO | | attachmentID | uint64 | PATH | Attachment ID | N/A | YES | | name | string | PATH | File name | N/A | YES | | kind | string | PATH | Attachment kind | N/A | YES | @@ -103,8 +103,8 @@ | ext | string | PATH | Preview extension/format | N/A | YES | | kind | string | PATH | Attachment kind | N/A | YES | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | -| sign | string | GET | Signature | N/A | YES | -| userID | uint64 | GET | User ID | N/A | YES | +| sign | string | GET | Signature | N/A | NO | +| userID | uint64 | GET | User ID | N/A | NO | --- From fefc75f39292144ce0bebae410dc51987f1959dd Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Wed, 1 May 2019 10:24:31 +0200 Subject: [PATCH 13/14] Refactor & fix create & read endpoints --- compose/rest/chart.go | 4 +++- compose/rest/module.go | 21 ++++++++++++++------- compose/rest/page.go | 4 +++- compose/rest/record.go | 24 ++++++++++++++++++------ compose/rest/trigger.go | 23 +++++++++++++---------- 5 files changed, 51 insertions(+), 25 deletions(-) diff --git a/compose/rest/chart.go b/compose/rest/chart.go index 683670867..e08552114 100644 --- a/compose/rest/chart.go +++ b/compose/rest/chart.go @@ -66,7 +66,9 @@ func (ctrl Chart) Create(ctx context.Context, r *request.ChartCreate) (interface } func (ctrl Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, error) { - return ctrl.chart.With(ctx).FindByID(r.NamespaceID, r.ChartID) + mod, err := ctrl.chart.With(ctx).FindByID(r.NamespaceID, r.ChartID) + return ctrl.makePayload(ctx, mod, err) + } func (ctrl Chart) Update(ctx context.Context, r *request.ChartUpdate) (interface{}, error) { diff --git a/compose/rest/module.go b/compose/rest/module.go index f1a5435cb..1d625a73c 100644 --- a/compose/rest/module.go +++ b/compose/rest/module.go @@ -55,16 +55,23 @@ func (ctrl *Module) List(ctx context.Context, r *request.ModuleList) (interface{ } func (ctrl *Module) Read(ctx context.Context, r *request.ModuleRead) (interface{}, error) { - return ctrl.module.With(ctx).FindByID(r.NamespaceID, r.ModuleID) + mod, err := ctrl.module.With(ctx).FindByID(r.NamespaceID, r.ModuleID) + return ctrl.makePayload(ctx, mod, err) } func (ctrl *Module) Create(ctx context.Context, r *request.ModuleCreate) (interface{}, error) { - item := &types.Module{ - Name: r.Name, - Fields: r.Fields, - Meta: r.Meta, - } - return ctrl.module.With(ctx).Create(item) + var ( + err error + mod = &types.Module{ + NamespaceID: r.NamespaceID, + Name: r.Name, + Fields: r.Fields, + Meta: r.Meta, + } + ) + + mod, err = ctrl.module.With(ctx).Create(mod) + return ctrl.makePayload(ctx, mod, err) } func (ctrl *Module) Update(ctx context.Context, r *request.ModuleUpdate) (interface{}, error) { diff --git a/compose/rest/page.go b/compose/rest/page.go index 734ec0c32..746646352 100644 --- a/compose/rest/page.go +++ b/compose/rest/page.go @@ -76,7 +76,9 @@ func (ctrl *Page) Create(ctx context.Context, r *request.PageCreate) (interface{ } func (ctrl *Page) Read(ctx context.Context, r *request.PageRead) (interface{}, error) { - return ctrl.page.With(ctx).FindByID(r.NamespaceID, r.PageID) + mod, err := ctrl.page.With(ctx).FindByID(r.NamespaceID, r.PageID) + return ctrl.makePayload(ctx, mod, err) + } func (ctrl *Page) Reorder(ctx context.Context, r *request.PageReorder) (interface{}, error) { diff --git a/compose/rest/record.go b/compose/rest/record.go index c2c3d91fe..3d0598367 100644 --- a/compose/rest/record.go +++ b/compose/rest/record.go @@ -59,18 +59,30 @@ func (ctrl *Record) List(ctx context.Context, r *request.RecordList) (interface{ } func (ctrl *Record) Read(ctx context.Context, r *request.RecordRead) (interface{}, error) { - return ctrl.record.With(ctx).FindByID(r.NamespaceID, r.RecordID) + mod, err := ctrl.record.With(ctx).FindByID(r.NamespaceID, r.RecordID) + + return ctrl.makePayload(ctx, mod, err) } func (ctrl *Record) Create(ctx context.Context, r *request.RecordCreate) (interface{}, error) { - return ctrl.record.With(ctx).Create(&types.Record{ModuleID: r.ModuleID, Values: r.Values}) + mod, err := ctrl.record.With(ctx).Create(&types.Record{ + NamespaceID: r.NamespaceID, + ModuleID: r.ModuleID, + Values: r.Values, + }) + + return ctrl.makePayload(ctx, mod, err) } func (ctrl *Record) Update(ctx context.Context, r *request.RecordUpdate) (interface{}, error) { - return ctrl.record.With(ctx).Update(&types.Record{ - ID: r.RecordID, - ModuleID: r.ModuleID, - Values: r.Values}) + mod, err := ctrl.record.With(ctx).Update(&types.Record{ + ID: r.RecordID, + NamespaceID: r.NamespaceID, + ModuleID: r.ModuleID, + Values: r.Values, + }) + + return ctrl.makePayload(ctx, mod, err) } func (ctrl *Record) Delete(ctx context.Context, r *request.RecordDelete) (interface{}, error) { diff --git a/compose/rest/trigger.go b/compose/rest/trigger.go index 78392d03d..ec3b2da1d 100644 --- a/compose/rest/trigger.go +++ b/compose/rest/trigger.go @@ -49,22 +49,25 @@ func (ctrl Trigger) List(ctx context.Context, r *request.TriggerList) (interface } func (ctrl Trigger) Create(ctx context.Context, r *request.TriggerCreate) (interface{}, error) { - var err error - ns := &types.Trigger{ - NamespaceID: r.NamespaceID, - ModuleID: r.ModuleID, - Name: r.Name, - Actions: r.Actions, - Enabled: r.Enabled, - Source: r.Source, - } + var ( + err error + ns = &types.Trigger{ + NamespaceID: r.NamespaceID, + ModuleID: r.ModuleID, + Name: r.Name, + Actions: r.Actions, + Enabled: r.Enabled, + Source: r.Source, + } + ) ns, err = ctrl.trigger.With(ctx).Create(ns) return ctrl.makePayload(ctx, ns, err) } func (ctrl Trigger) Read(ctx context.Context, r *request.TriggerRead) (interface{}, error) { - return ctrl.trigger.With(ctx).FindByID(r.NamespaceID, r.TriggerID) + mod, err := ctrl.trigger.With(ctx).FindByID(r.NamespaceID, r.TriggerID) + return ctrl.makePayload(ctx, mod, err) } func (ctrl Trigger) Update(ctx context.Context, r *request.TriggerUpdate) (interface{}, error) { From c467edb02b71ff6e73e4eb4ca8c500d574869ce5 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sat, 4 May 2019 12:32:55 +0200 Subject: [PATCH 14/14] Fixing trigger saving, make (old) migration step more robust (drop if exists) --- .gitignore | 1 + compose/rest/trigger.go | 2 ++ system/db/mysql/static.go | 2 +- system/db/schema/mysql/20190326122000.settings.up.sql | 4 ++-- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 857e64f86..8618e5b13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /coverage.txt /*.iml /.env* +/.dev* /.cover* /public_html /build diff --git a/compose/rest/trigger.go b/compose/rest/trigger.go index ec3b2da1d..b069ca345 100644 --- a/compose/rest/trigger.go +++ b/compose/rest/trigger.go @@ -76,6 +76,8 @@ func (ctrl Trigger) Update(ctx context.Context, r *request.TriggerUpdate) (inter err error ) + mod.ID = r.TriggerID + mod.NamespaceID = r.NamespaceID mod.ModuleID = r.ModuleID mod.Name = r.Name mod.Actions = r.Actions diff --git a/system/db/mysql/static.go b/system/db/mysql/static.go index f6b7ec230..a439cda58 100644 --- a/system/db/mysql/static.go +++ b/system/db/mysql/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package mysql -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8-- all known organisations (crust instances) and our relation towards them\nCREATE TABLE organisations (\n id BIGINT UNSIGNED NOT NULL,\n fqn TEXT NOT NULL, -- fully qualified name of the organisation\n name TEXT NOT NULL, -- display name of the organisation\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- organisation soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE settings (\n name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys',\n value TEXT COMMENT 'Setting value',\n\n PRIMARY KEY (name)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE users (\n id BIGINT UNSIGNED NOT NULL,\n email TEXT NOT NULL,\n username TEXT NOT NULL,\n password TEXT NOT NULL,\n name TEXT NOT NULL,\n handle TEXT NOT NULL,\n meta JSON NOT NULL,\n satosa_id CHAR(36) NULL,\n\n rel_organisation BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n suspended_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE UNIQUE INDEX uid_satosa ON users (satosa_id);\n\n-- Keeps all known teams\nCREATE TABLE teams (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the team\n handle TEXT NOT NULL, -- team handle string\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- team soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps team memberships\nCREATE TABLE team_members (\n rel_team BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (rel_team, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xedzU\x8am \x00\x00m \x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE teams RENAME TO sys_team;\nALTER TABLE organisations RENAME TO sys_organisation;\nALTER TABLE team_members RENAME TO sys_team_member;\nALTER TABLE users RENAME TO sys_user;PK\x07\x08\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8# add field to manage user type (bot support)\nALTER TABLE `sys_user` ADD `kind` VARCHAR(8) NOT NULL DEFAULT '' AFTER `handle`;\n\n# add field to manage \"ownership\" (get all bots created by user)\nALTER TABLE `sys_user` ADD `rel_user_id` BIGINT UNSIGNED NOT NULL AFTER `rel_organisation`, ADD INDEX (`rel_user_id`);\nPK\x07\x089\xa0\xdat8\x01\x00\x008\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` DROP INDEX `uid_satosa`, ADD INDEX `uid_satosa` (`satosa_id`) USING BTREE;PK\x07\x08\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00 \x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE sys_credentials (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n label TEXT NOT NULL COMMENT 'something we can differentiate credentials by',\n kind VARCHAR(128) NOT NULL COMMENT 'hash, facebook, gplus, github, linkedin ...',\n credentials TEXT NOT NULL COMMENT 'crypted/hashed passwords, secrets, social profile ID',\n meta JSON NOT NULL,\n expires_at DATETIME NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE INDEX idx_owner ON sys_credentials (rel_owner);\nPK\x07\x08f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` MODIFY `password` TEXT NULL;\nPK\x07\x080V\x13\x0f4\x00\x00\x004\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00 \x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `sys_rules` (\n `rel_team` BIGINT UNSIGNED NOT NULL,\n `resource` VARCHAR(128) NOT NULL,\n `operation` VARCHAR(128) NOT NULL,\n `value` TINYINT(1) NOT NULL,\n\n PRIMARY KEY (`rel_team`, `resource`, `operation`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\x05\x10[\x91\x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190221001051.rename-team-to-role.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE sys_team RENAME TO sys_role;\nALTER TABLE sys_team_member RENAME TO sys_role_member;\n\nALTER TABLE `sys_role_member` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;\nALTER TABLE `sys_rules` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;\nPK\x07\x08s-\x98\xd0\x13\x01\x00\x00\x13\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00,\x00 \x0020190226160000.system_roles_and_rules.up.sqlUT\x05\x00\x01\x80Cm8REPLACE INTO `sys_role` (`id`, `name`, `handle`) VALUES\n (1, 'Everyone', 'everyone'),\n (2, 'Administrators', 'admins');\n\n-- Value: Allow (2), Deny (1), Inherit(0),\nREPLACE INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES\n-- Everyone\n (1, 'compose:*', 'access', 2),\n (1, 'messaging:*', 'access', 2),\n-- Admins\n (2, 'compose', 'namespace.create', 2),\n (2, 'compose', 'access', 2),\n (2, 'compose', 'grant', 2),\n (2, 'compose:namespace:*', 'page.create', 2),\n (2, 'compose:namespace:*', 'read', 2),\n (2, 'compose:namespace:*', 'update', 2),\n (2, 'compose:namespace:*', 'delete', 2),\n (2, 'compose:namespace:*', 'module.create', 2),\n (2, 'compose:namespace:*', 'chart.create', 2),\n (2, 'compose:namespace:*', 'trigger.create', 2),\n (2, 'compose:chart:*', 'read', 2),\n (2, 'compose:chart:*', 'update', 2),\n (2, 'compose:chart:*', 'delete', 2),\n (2, 'compose:trigger:*', 'read', 2),\n (2, 'compose:trigger:*', 'update', 2),\n (2, 'compose:trigger:*', 'delete', 2),\n (2, 'compose:page:*', 'read', 2),\n (2, 'compose:page:*', 'update', 2),\n (2, 'compose:page:*', 'delete', 2),\n (2, 'system', 'access', 2),\n (2, 'system', 'grant', 2),\n (2, 'system', 'organisation.create', 2),\n (2, 'system', 'role.create', 2),\n (2, 'system:organisation:*', 'access', 2),\n (2, 'system:role:*', 'read', 2),\n (2, 'system:role:*', 'update', 2),\n (2, 'system:role:*', 'delete', 2),\n (2, 'system:role:*', 'members.manage', 2),\n (2, 'messaging', 'access', 2),\n (2, 'messaging', 'grant', 2),\n (2, 'messaging', 'channel.public.create', 2),\n (2, 'messaging', 'channel.private.create', 2),\n (2, 'messaging', 'channel.group.create', 2),\n (2, 'messaging:channel:*', 'update', 2),\n (2, 'messaging:channel:*', 'leave', 2),\n (2, 'messaging:channel:*', 'read', 2),\n (2, 'messaging:channel:*', 'join', 2),\n (2, 'messaging:channel:*', 'delete', 2),\n (2, 'messaging:channel:*', 'undelete', 2),\n (2, 'messaging:channel:*', 'archive', 2),\n (2, 'messaging:channel:*', 'unarchive', 2),\n (2, 'messaging:channel:*', 'members.manage', 2),\n (2, 'messaging:channel:*', 'webhooks.manage', 2),\n (2, 'messaging:channel:*', 'attachments.manage', 2),\n (2, 'messaging:channel:*', 'message.attach', 2),\n (2, 'messaging:channel:*', 'message.update.all', 2),\n (2, 'messaging:channel:*', 'message.update.own', 2),\n (2, 'messaging:channel:*', 'message.delete.all', 2),\n (2, 'messaging:channel:*', 'message.delete.own', 2),\n (2, 'messaging:channel:*', 'message.embed', 2),\n (2, 'messaging:channel:*', 'message.send', 2),\n (2, 'messaging:channel:*', 'message.reply', 2),\n (2, 'messaging:channel:*', 'message.react', 2),\n (2, 'compose:module:*', 'read', 2),\n (2, 'compose:module:*', 'update', 2),\n (2, 'compose:module:*', 'delete', 2),\n (2, 'compose:module:*', 'record.create', 2),\n (2, 'compose:module:*', 'record.read', 2),\n (2, 'compose:module:*', 'record.update', 2),\n (2, 'compose:module:*', 'record.delete', 2);\nPK\x07\x08z\",\xe8t\x0b\x00\x00t\x0b\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00 \x0020190306205033.applications.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE sys_application (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n name TEXT NOT NULL COMMENT 'something we can differentiate application by',\n enabled BOOL NOT NULL,\n\n unify JSON NULL COMMENT 'unify specific settings',\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n\nREPLACE INTO `sys_application` (`id`, `name`, `enabled`, `rel_owner`, `unify`) VALUES\n( 1, 'Crust Messaging', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/messaging/\", \"listed\": true}'\n),\n( 2, 'Crust CRM', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/crm/\", \"listed\": true}'\n),\n( 3, 'Crust Admin Area', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/admin/\", \"listed\": true}'\n),\n( 4, 'Corteza Jitsi Bridge', true, 0,\n '{\"logo\": \"/applications/jitsi.png\", \"icon\": \"/applications/jitsi_icon.png\", \"url\": \"/bridge/jitsi/\", \"listed\": true}'\n),\n( 5, 'Google Maps', true, 0,\n '{\"logo\": \"/applications/google_maps.png\", \"icon\": \"/applications/google_maps_icon.png\", \"url\": \"/bridge/google-maps/\", \"listed\": true}'\n);\n\n-- Allow admin access to applications\nINSERT INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES\n (1, 'system:application:*', 'read', 2),\n (2, 'system', 'application.create', 2),\n (2, 'system:application:*', 'read', 2),\n (2, 'system:application:*', 'update', 2),\n (2, 'system:application:*', 'delete', 2)\n;\nPK\x07\x08\xce\xaf\xb0\x07\x11\x07\x00\x00\x11\x07\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00 \x0020190326122000.settings.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `settings`;\n\nCREATE TABLE `sys_settings` (\n rel_owner BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Value owner, 0 for global settings',\n name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys',\n value JSON COMMENT 'Setting value',\n\n updated_at DATETIME NOT NULL DEFAULT NOW() COMMENT 'When was the value updated',\n updated_by BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Who created/updated the value',\n\n PRIMARY KEY (name, rel_owner)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xba\xa4\xab\xaf\\\x02\x00\x00\\\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190403113201.users-cleanup.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` DROP `password`;\nALTER TABLE `sys_user` DROP `satosa_id`;\nALTER TABLE `sys_credentials` ADD `last_used_at` DATETIME NULL;\nPK\x07\x088\x92\x0fs\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190405090000.internal-auth.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` ADD `email_confirmed` BOOLEAN NOT NULL DEFAULT FALSE;\nPK\x07\x08\x8fQs\x8cM\x00\x00\x00M\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` TEXT NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sqlPK\x07\x08s\xd4N*.\x00\x00\x00.\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xedzU\x8am \x00\x00m \x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbe \x00\x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9\xa0\xdat8\x01\x00\x008\x01\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd8\n\x00\x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81t\x0c\x00\x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x0d\x00\x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(0V\x13\x0f4\x00\x00\x004\x00\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81+\x11\x00\x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x05\x10[\x91\x05\x01\x00\x00\x05\x01\x00\x00\x1b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbf\x11\x00\x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s-\x98\xd0\x13\x01\x00\x00\x13\x01\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x16\x13\x00\x0020190221001051.rename-team-to-role.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(z\",\xe8t\x0b\x00\x00t\x0b\x00\x00,\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x89\x14\x00\x0020190226160000.system_roles_and_rules.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xaf\xb0\x07\x11\x07\x00\x00\x11\x07\x00\x00\"\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81` \x00\x0020190306205033.applications.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xba\xa4\xab\xaf\\\x02\x00\x00\\\x02\x00\x00\x1e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xca'\x00\x0020190326122000.settings.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(8\x92\x0fs\x91\x00\x00\x00\x91\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81{*\x00\x0020190403113201.users-cleanup.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x8fQs\x8cM\x00\x00\x00M\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81f+\x00\x0020190405090000.internal-auth.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x0d,\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s\xd4N*.\x00\x00\x00.\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\xca-\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x0f\x00\x0f\x00/\x05\x00\x005.\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8-- all known organisations (crust instances) and our relation towards them\nCREATE TABLE organisations (\n id BIGINT UNSIGNED NOT NULL,\n fqn TEXT NOT NULL, -- fully qualified name of the organisation\n name TEXT NOT NULL, -- display name of the organisation\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- organisation soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE settings (\n name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys',\n value TEXT COMMENT 'Setting value',\n\n PRIMARY KEY (name)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE users (\n id BIGINT UNSIGNED NOT NULL,\n email TEXT NOT NULL,\n username TEXT NOT NULL,\n password TEXT NOT NULL,\n name TEXT NOT NULL,\n handle TEXT NOT NULL,\n meta JSON NOT NULL,\n satosa_id CHAR(36) NULL,\n\n rel_organisation BIGINT UNSIGNED NOT NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n suspended_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE UNIQUE INDEX uid_satosa ON users (satosa_id);\n\n-- Keeps all known teams\nCREATE TABLE teams (\n id BIGINT UNSIGNED NOT NULL,\n name TEXT NOT NULL, -- display name of the team\n handle TEXT NOT NULL, -- team handle string\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n archived_at DATETIME NULL,\n deleted_at DATETIME NULL, -- team soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- Keeps team memberships\nCREATE TABLE team_members (\n rel_team BIGINT UNSIGNED NOT NULL REFERENCES organisation(id),\n rel_user BIGINT UNSIGNED NOT NULL,\n\n PRIMARY KEY (rel_team, rel_user)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xedzU\x8am \x00\x00m \x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE teams RENAME TO sys_team;\nALTER TABLE organisations RENAME TO sys_organisation;\nALTER TABLE team_members RENAME TO sys_team_member;\nALTER TABLE users RENAME TO sys_user;PK\x07\x08\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8# add field to manage user type (bot support)\nALTER TABLE `sys_user` ADD `kind` VARCHAR(8) NOT NULL DEFAULT '' AFTER `handle`;\n\n# add field to manage \"ownership\" (get all bots created by user)\nALTER TABLE `sys_user` ADD `rel_user_id` BIGINT UNSIGNED NOT NULL AFTER `rel_organisation`, ADD INDEX (`rel_user_id`);\nPK\x07\x089\xa0\xdat8\x01\x00\x008\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\x00 \x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` DROP INDEX `uid_satosa`, ADD INDEX `uid_satosa` (`satosa_id`) USING BTREE;PK\x07\x08\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00 \x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8-- Keeps all known users, home and external organisation\n-- changes are stored in audit log\nCREATE TABLE sys_credentials (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n label TEXT NOT NULL COMMENT 'something we can differentiate credentials by',\n kind VARCHAR(128) NOT NULL COMMENT 'hash, facebook, gplus, github, linkedin ...',\n credentials TEXT NOT NULL COMMENT 'crypted/hashed passwords, secrets, social profile ID',\n meta JSON NOT NULL,\n expires_at DATETIME NULL,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE INDEX idx_owner ON sys_credentials (rel_owner);\nPK\x07\x08f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` MODIFY `password` TEXT NULL;\nPK\x07\x080V\x13\x0f4\x00\x00\x004\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1b\x00 \x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `sys_rules` (\n `rel_team` BIGINT UNSIGNED NOT NULL,\n `resource` VARCHAR(128) NOT NULL,\n `operation` VARCHAR(128) NOT NULL,\n `value` TINYINT(1) NOT NULL,\n\n PRIMARY KEY (`rel_team`, `resource`, `operation`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\x05\x10[\x91\x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\x00 \x0020190221001051.rename-team-to-role.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE sys_team RENAME TO sys_role;\nALTER TABLE sys_team_member RENAME TO sys_role_member;\n\nALTER TABLE `sys_role_member` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;\nALTER TABLE `sys_rules` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;\nPK\x07\x08s-\x98\xd0\x13\x01\x00\x00\x13\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00,\x00 \x0020190226160000.system_roles_and_rules.up.sqlUT\x05\x00\x01\x80Cm8REPLACE INTO `sys_role` (`id`, `name`, `handle`) VALUES\n (1, 'Everyone', 'everyone'),\n (2, 'Administrators', 'admins');\n\n-- Value: Allow (2), Deny (1), Inherit(0),\nREPLACE INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES\n-- Everyone\n (1, 'compose:*', 'access', 2),\n (1, 'messaging:*', 'access', 2),\n-- Admins\n (2, 'compose', 'namespace.create', 2),\n (2, 'compose', 'access', 2),\n (2, 'compose', 'grant', 2),\n (2, 'compose:namespace:*', 'page.create', 2),\n (2, 'compose:namespace:*', 'read', 2),\n (2, 'compose:namespace:*', 'update', 2),\n (2, 'compose:namespace:*', 'delete', 2),\n (2, 'compose:namespace:*', 'module.create', 2),\n (2, 'compose:namespace:*', 'chart.create', 2),\n (2, 'compose:namespace:*', 'trigger.create', 2),\n (2, 'compose:chart:*', 'read', 2),\n (2, 'compose:chart:*', 'update', 2),\n (2, 'compose:chart:*', 'delete', 2),\n (2, 'compose:trigger:*', 'read', 2),\n (2, 'compose:trigger:*', 'update', 2),\n (2, 'compose:trigger:*', 'delete', 2),\n (2, 'compose:page:*', 'read', 2),\n (2, 'compose:page:*', 'update', 2),\n (2, 'compose:page:*', 'delete', 2),\n (2, 'system', 'access', 2),\n (2, 'system', 'grant', 2),\n (2, 'system', 'organisation.create', 2),\n (2, 'system', 'role.create', 2),\n (2, 'system:organisation:*', 'access', 2),\n (2, 'system:role:*', 'read', 2),\n (2, 'system:role:*', 'update', 2),\n (2, 'system:role:*', 'delete', 2),\n (2, 'system:role:*', 'members.manage', 2),\n (2, 'messaging', 'access', 2),\n (2, 'messaging', 'grant', 2),\n (2, 'messaging', 'channel.public.create', 2),\n (2, 'messaging', 'channel.private.create', 2),\n (2, 'messaging', 'channel.group.create', 2),\n (2, 'messaging:channel:*', 'update', 2),\n (2, 'messaging:channel:*', 'leave', 2),\n (2, 'messaging:channel:*', 'read', 2),\n (2, 'messaging:channel:*', 'join', 2),\n (2, 'messaging:channel:*', 'delete', 2),\n (2, 'messaging:channel:*', 'undelete', 2),\n (2, 'messaging:channel:*', 'archive', 2),\n (2, 'messaging:channel:*', 'unarchive', 2),\n (2, 'messaging:channel:*', 'members.manage', 2),\n (2, 'messaging:channel:*', 'webhooks.manage', 2),\n (2, 'messaging:channel:*', 'attachments.manage', 2),\n (2, 'messaging:channel:*', 'message.attach', 2),\n (2, 'messaging:channel:*', 'message.update.all', 2),\n (2, 'messaging:channel:*', 'message.update.own', 2),\n (2, 'messaging:channel:*', 'message.delete.all', 2),\n (2, 'messaging:channel:*', 'message.delete.own', 2),\n (2, 'messaging:channel:*', 'message.embed', 2),\n (2, 'messaging:channel:*', 'message.send', 2),\n (2, 'messaging:channel:*', 'message.reply', 2),\n (2, 'messaging:channel:*', 'message.react', 2),\n (2, 'compose:module:*', 'read', 2),\n (2, 'compose:module:*', 'update', 2),\n (2, 'compose:module:*', 'delete', 2),\n (2, 'compose:module:*', 'record.create', 2),\n (2, 'compose:module:*', 'record.read', 2),\n (2, 'compose:module:*', 'record.update', 2),\n (2, 'compose:module:*', 'record.delete', 2);\nPK\x07\x08z\",\xe8t\x0b\x00\x00t\x0b\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\x00 \x0020190306205033.applications.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE sys_application (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),\n name TEXT NOT NULL COMMENT 'something we can differentiate application by',\n enabled BOOL NOT NULL,\n\n unify JSON NULL COMMENT 'unify specific settings',\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL, -- user soft delete\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n\nREPLACE INTO `sys_application` (`id`, `name`, `enabled`, `rel_owner`, `unify`) VALUES\n( 1, 'Crust Messaging', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/messaging/\", \"listed\": true}'\n),\n( 2, 'Crust CRM', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/crm/\", \"listed\": true}'\n),\n( 3, 'Crust Admin Area', true, 0,\n '{\"logo\": \"/applications/crust.jpg\", \"icon\": \"/applications/crust_favicon.png\", \"url\": \"/admin/\", \"listed\": true}'\n),\n( 4, 'Corteza Jitsi Bridge', true, 0,\n '{\"logo\": \"/applications/jitsi.png\", \"icon\": \"/applications/jitsi_icon.png\", \"url\": \"/bridge/jitsi/\", \"listed\": true}'\n),\n( 5, 'Google Maps', true, 0,\n '{\"logo\": \"/applications/google_maps.png\", \"icon\": \"/applications/google_maps_icon.png\", \"url\": \"/bridge/google-maps/\", \"listed\": true}'\n);\n\n-- Allow admin access to applications\nINSERT INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES\n (1, 'system:application:*', 'read', 2),\n (2, 'system', 'application.create', 2),\n (2, 'system:application:*', 'read', 2),\n (2, 'system:application:*', 'update', 2),\n (2, 'system:application:*', 'delete', 2)\n;\nPK\x07\x08\xce\xaf\xb0\x07\x11\x07\x00\x00\x11\x07\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00 \x0020190326122000.settings.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS `settings`;\n\nCREATE TABLE IF NOT EXISTS `sys_settings` (\n rel_owner BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Value owner, 0 for global settings',\n name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys',\n value JSON COMMENT 'Setting value',\n\n updated_at DATETIME NOT NULL DEFAULT NOW() COMMENT 'When was the value updated',\n updated_by BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Who created/updated the value',\n\n PRIMARY KEY (name, rel_owner)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08`\xcb\x1b\x81t\x02\x00\x00t\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190403113201.users-cleanup.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` DROP `password`;\nALTER TABLE `sys_user` DROP `satosa_id`;\nALTER TABLE `sys_credentials` ADD `last_used_at` DATETIME NULL;\nPK\x07\x088\x92\x0fs\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190405090000.internal-auth.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `sys_user` ADD `email_confirmed` BOOLEAN NOT NULL DEFAULT FALSE;\nPK\x07\x08\x8fQs\x8cM\x00\x00\x00M\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` TEXT NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sqlPK\x07\x08s\xd4N*.\x00\x00\x00.\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xedzU\x8am \x00\x00m \x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\xc4\x87\xe8\xb5\x00\x00\x00\xb5\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbe \x00\x0020181124181811.rename_and_prefix_tables.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9\xa0\xdat8\x01\x00\x008\x01\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd8\n\x00\x0020181125100429.add_user_kind_and_owner.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xf9\xd3ga\x00\x00\x00a\x00\x00\x00-\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81t\x0c\x00\x0020181125153544.satosa_index_not_unique.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x1f\x08\xd0\x9a\x03\x00\x00\x9a\x03\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x0d\x00\x0020181208140000.credentials.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(0V\x13\x0f4\x00\x00\x004\x00\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81+\x11\x00\x0020190103203201.users-password-null.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x05\x10[\x91\x05\x01\x00\x00\x05\x01\x00\x00\x1b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xbf\x11\x00\x0020190116102104.rules.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s-\x98\xd0\x13\x01\x00\x00\x13\x01\x00\x00)\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x16\x13\x00\x0020190221001051.rename-team-to-role.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(z\",\xe8t\x0b\x00\x00t\x0b\x00\x00,\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x89\x14\x00\x0020190226160000.system_roles_and_rules.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xaf\xb0\x07\x11\x07\x00\x00\x11\x07\x00\x00\"\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81` \x00\x0020190306205033.applications.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(`\xcb\x1b\x81t\x02\x00\x00t\x02\x00\x00\x1e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xca'\x00\x0020190326122000.settings.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(8\x92\x0fs\x91\x00\x00\x00\x91\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x93*\x00\x0020190403113201.users-cleanup.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x8fQs\x8cM\x00\x00\x00M\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81~+\x00\x0020190405090000.internal-auth.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x0d\xa5T2x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81%,\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(s\xd4N*.\x00\x00\x00.\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\xe2-\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x0f\x00\x0f\x00/\x05\x00\x00M.\x00\x00\x00\x00" diff --git a/system/db/schema/mysql/20190326122000.settings.up.sql b/system/db/schema/mysql/20190326122000.settings.up.sql index f98ab175c..9e06d8b05 100644 --- a/system/db/schema/mysql/20190326122000.settings.up.sql +++ b/system/db/schema/mysql/20190326122000.settings.up.sql @@ -1,6 +1,6 @@ -DROP TABLE `settings`; +DROP TABLE IF EXISTS `settings`; -CREATE TABLE `sys_settings` ( +CREATE TABLE IF NOT EXISTS `sys_settings` ( rel_owner BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Value owner, 0 for global settings', name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys', value JSON COMMENT 'Setting value',