From 71a78d53a5bd55390c709324eb9a2ebb4c0ad047 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Tue, 17 Sep 2019 21:21:02 +0200 Subject: [PATCH] Add support for handle, fix tests --- compose/compose.go | 10 +++ compose/db/mysql/static.go | 2 +- .../mysql/20190917080000.add-handles.up.sql | 3 + compose/repository/chart.go | 22 +++++- compose/repository/module.go | 65 ++++++++--------- compose/repository/namespace.go | 4 +- compose/repository/page.go | 23 ++++++- compose/repository/repository.go | 15 ++++ compose/service/chart.go | 52 ++++++++++++-- compose/service/error.go | 1 + compose/service/module.go | 69 ++++++++++++++++--- compose/service/namespace.go | 49 +++++++++---- compose/service/page.go | 53 ++++++++++---- compose/types/namespace.go | 3 +- system/service/error.go | 1 + system/service/role.go | 45 +++++------- system/service/user.go | 38 +++------- system/system.go | 3 + tests/compose/chart_test.go | 19 ++++- tests/compose/module_test.go | 17 +++++ tests/compose/namespace_test.go | 19 ++++- tests/compose/page_test.go | 17 +++++ tests/system/main_test.go | 10 +++ tests/system/role_test.go | 69 +++++++++++++++---- tests/system/user_test.go | 3 +- 25 files changed, 452 insertions(+), 160 deletions(-) create mode 100644 compose/db/schema/mysql/20190917080000.add-handles.up.sql diff --git a/compose/compose.go b/compose/compose.go index 3a4b1e4fc..cb80da608 100644 --- a/compose/compose.go +++ b/compose/compose.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/titpetric/factory" + "github.com/cortezaproject/corteza-server/compose/commands" migrate "github.com/cortezaproject/corteza-server/compose/db" "github.com/cortezaproject/corteza-server/compose/rest" "github.com/cortezaproject/corteza-server/compose/service" @@ -63,6 +64,15 @@ func Configure() *cli.Config { rest.MountRoutes, }, + AdtSubCommands: cli.CommandMakers{ + func(ctx context.Context, c *cli.Config) *cobra.Command { + return commands.Importer(ctx, c) + }, + func(ctx context.Context, c *cli.Config) *cobra.Command { + return commands.Exporter(ctx, c) + }, + }, + ProvisionMigrateDatabase: cli.Runners{ func(ctx context.Context, cmd *cobra.Command, c *cli.Config) error { var db, err = factory.Database.Get(compose) diff --git a/compose/db/mysql/static.go b/compose/db/mysql/static.go index 76cb58e0f..e6d7a3e03 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 IGNORE INTO compose_namespace (id, name, slug, enabled, meta)\n 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\xfc\xdb\xfd\xdde \x00\x00e \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#\x00 \x0020190514090000.module_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE compose_module_form\n RENAME TO compose_module_field;\n\n-- Remove orphaned and invalid fields\nDELETE FROM `compose_module_field` WHERE `module_id` NOT IN (SELECT `id` FROM `compose_module`) OR `name` = '';\n\n-- Order and consistency.\nALTER TABLE `compose_module_field`\n ADD COLUMN `id` BIGINT UNSIGNED NOT NULL FIRST,\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 RENAME COLUMN `module_id` TO `rel_module`,\n RENAME COLUMN `json` TO `options`;\n\n-- Generate IDs for the new field, use module, offset by one (just to start with a different ID)\n-- and use place (0 based, +1 for every field, expecting to be unique per module because of the existing pkey)\nUPDATE `compose_module_field` SET id = rel_module + 1 + place;\n\n-- Drop old primary key (module_id, place)\nALTER TABLE `compose_module_field` DROP PRIMARY KEY, ADD PRIMARY KEY(`id`);\n\n-- Foreign key\nALTER TABLE `compose_module_field`\n ADD CONSTRAINT `compose_module`\n FOREIGN KEY (`rel_module`)\n REFERENCES `compose_module` (`id`);\n\n-- And unique indexes for module+place/name combos.\nCREATE UNIQUE INDEX uid_compose_module_field_place ON compose_module_field (`rel_module`, `place`);\nCREATE UNIQUE INDEX uid_compose_module_field_name ON compose_module_field (`rel_module`, `name`);\nPK\x07\x08\xb1(\xbb\xf0\x8d\x05\x00\x00\x8d\x05\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 \x0020190526090000.permissions.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS compose_permission_rules (\n rel_role BIGINT UNSIGNED NOT NULL,\n resource VARCHAR(128) NOT NULL,\n operation VARCHAR(128) NOT NULL,\n access TINYINT(1) NOT NULL,\n\n PRIMARY KEY (rel_role, resource, operation)\n) ENGINE=InnoDB;\nPK\x07\x08\"\xd8\xe5H\x12\x01\x00\x00\x12\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 \x0020190701090000.automation.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS compose_automation_trigger;\nDROP TABLE IF EXISTS compose_automation_script;\n\nCREATE TABLE IF NOT EXISTS compose_automation_script (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL DEFAULT 'unnamed' COMMENT 'The name of the script',\n `source` TEXT NOT NULL COMMENT 'Source code for the script',\n `source_ref` VARCHAR(200) NOT NULL COMMENT 'Where is the script located (if remote)',\n `async` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Do we run this script asynchronously?',\n `rel_runner` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Who is running the script? 0 for invoker',\n `run_in_ua` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Run this script inside user-agent environment',\n `timeout` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Any explicit timeout set for this script (milliseconds)?',\n `critical` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'Is it critical that this script is executed successfully',\n `enabled` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'Is this script enabled?',\n\n `created_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `updated_at` DATETIME NULL DEFAULT NULL,\n `deleted_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `deleted_at` DATETIME NULL DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE IF NOT EXISTS compose_automation_trigger (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `rel_script` BIGINT(20) UNSIGNED NOT NULL COMMENT 'Script that is triggered',\n\n `resource` VARCHAR(128) NOT NULL COMMENT 'Resource triggering the event',\n `event` VARCHAR(128) NOT NULL COMMENT 'Event triggered',\n `event_condition`\n TEXT NOT NULL COMMENT 'Trigger condition',\n `enabled` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'Trigger enabled?',\n\n `weight` INT NOT NULL DEFAULT 0,\n\n `created_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `updated_at` DATETIME NULL DEFAULT NULL,\n `deleted_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `deleted_at` DATETIME NULL DEFAULT NULL,\n\n CONSTRAINT `fk_script` FOREIGN KEY (`rel_script`) REFERENCES `compose_automation_script` (`id`),\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n\n\n# Migrate old triggers into scripts\nINSERT INTO compose_automation_script (id, name, source, source_ref, run_in_ua, critical, enabled, created_at, updated_at, deleted_at)\nSELECT id, name, source, '', true, false, enabled, created_at, updated_at, deleted_at from compose_trigger;\n\n# Migrate old triggers into new triggers\nINSERT INTO compose_automation_trigger (id, event, resource, event_condition, rel_script, enabled, created_at, updated_at, deleted_at)\nSELECT id+seq, events.event, 'compose:record', rel_module, id, enabled, created_at, updated_at, deleted_at from compose_trigger AS t INNER JOIN\n ( SELECT 0 as seq, '' AS event\n UNION SELECT 1 as seq, 'manual' AS event\n UNION SELECT 2 as seq, 'beforeCreate' AS event\n UNION SELECT 3 as seq, 'afterCreate' AS event\n UNION SELECT 4 as seq, 'beforeUpdate' AS event\n UNION SELECT 5 as seq, 'afterUpdate' AS event\n UNION SELECT 6 as seq, 'beforeDelete' AS event\n UNION SELECT 7 as seq, 'afterDelete' AS event) AS events ON ((event = '' AND t.actions = '')\n OR (event <> '' AND t.actions LIKE concat('%',event,'%') ));\n# Normalize and cleanup\nUPDATE compose_automation_trigger SET event = 'manual' WHERE event = '';\nDELETE FROM compose_automation_trigger WHERE event_condition IN ('', '0') AND event <> 'manual';\n\nDROP TABLE IF EXISTS compose_trigger;\nPK\x07\x08c\xda\x17\xa4\x13\x11\x00\x00\x13\x11\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 \x0020190825090000.automation-namespace.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `compose_automation_script`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nUPDATE `compose_automation_script` SET `rel_namespace` = (SELECT MIN(id) FROM compose_namespace);\n\nALTER TABLE `compose_automation_script`\n ADD CONSTRAINT `compose_automation_script_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\nPK\x07\x08;#~I\x98\x01\x00\x00\x98\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 \x0020190912125228.field-default.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `compose_module_field`\n ADD `default_value` JSON DEFAULT NULL COMMENT 'Default value as a record value set.'\n AFTER `options`;\nPK\x07\x08&~D\xee\x8d\x00\x00\x00\x8d\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!(\xfc\xdb\xfd\xdde \x00\x00e \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\xe25\x00\x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb1(\xbb\xf0\x8d\x05\x00\x00\x8d\x05\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81o7\x00\x0020190514090000.module_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\xd8\xe5H\x12\x01\x00\x00\x12\x01\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81V=\x00\x0020190526090000.permissions.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(c\xda\x17\xa4\x13\x11\x00\x00\x13\x11\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81\xc0>\x00\x0020190701090000.automation.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(;#~I\x98\x01\x00\x00\x98\x01\x00\x00*\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81*P\x00\x0020190825090000.automation-namespace.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(&~D\xee\x8d\x00\x00\x00\x8d\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81#R\x00\x0020190912125228.field-default.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\nS\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\xc7T\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x1c\x00\x1c\x00G\n\x00\x003U\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 IGNORE INTO compose_namespace (id, name, slug, enabled, meta)\n 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\xfc\xdb\xfd\xdde \x00\x00e \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#\x00 \x0020190514090000.module_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE compose_module_form\n RENAME TO compose_module_field;\n\n-- Remove orphaned and invalid fields\nDELETE FROM `compose_module_field` WHERE `module_id` NOT IN (SELECT `id` FROM `compose_module`) OR `name` = '';\n\n-- Order and consistency.\nALTER TABLE `compose_module_field`\n ADD COLUMN `id` BIGINT UNSIGNED NOT NULL FIRST,\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 RENAME COLUMN `module_id` TO `rel_module`,\n RENAME COLUMN `json` TO `options`;\n\n-- Generate IDs for the new field, use module, offset by one (just to start with a different ID)\n-- and use place (0 based, +1 for every field, expecting to be unique per module because of the existing pkey)\nUPDATE `compose_module_field` SET id = rel_module + 1 + place;\n\n-- Drop old primary key (module_id, place)\nALTER TABLE `compose_module_field` DROP PRIMARY KEY, ADD PRIMARY KEY(`id`);\n\n-- Foreign key\nALTER TABLE `compose_module_field`\n ADD CONSTRAINT `compose_module`\n FOREIGN KEY (`rel_module`)\n REFERENCES `compose_module` (`id`);\n\n-- And unique indexes for module+place/name combos.\nCREATE UNIQUE INDEX uid_compose_module_field_place ON compose_module_field (`rel_module`, `place`);\nCREATE UNIQUE INDEX uid_compose_module_field_name ON compose_module_field (`rel_module`, `name`);\nPK\x07\x08\xb1(\xbb\xf0\x8d\x05\x00\x00\x8d\x05\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 \x0020190526090000.permissions.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS compose_permission_rules (\n rel_role BIGINT UNSIGNED NOT NULL,\n resource VARCHAR(128) NOT NULL,\n operation VARCHAR(128) NOT NULL,\n access TINYINT(1) NOT NULL,\n\n PRIMARY KEY (rel_role, resource, operation)\n) ENGINE=InnoDB;\nPK\x07\x08\"\xd8\xe5H\x12\x01\x00\x00\x12\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 \x0020190701090000.automation.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS compose_automation_trigger;\nDROP TABLE IF EXISTS compose_automation_script;\n\nCREATE TABLE IF NOT EXISTS compose_automation_script (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL DEFAULT 'unnamed' COMMENT 'The name of the script',\n `source` TEXT NOT NULL COMMENT 'Source code for the script',\n `source_ref` VARCHAR(200) NOT NULL COMMENT 'Where is the script located (if remote)',\n `async` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Do we run this script asynchronously?',\n `rel_runner` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Who is running the script? 0 for invoker',\n `run_in_ua` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Run this script inside user-agent environment',\n `timeout` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Any explicit timeout set for this script (milliseconds)?',\n `critical` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'Is it critical that this script is executed successfully',\n `enabled` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'Is this script enabled?',\n\n `created_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `updated_at` DATETIME NULL DEFAULT NULL,\n `deleted_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `deleted_at` DATETIME NULL DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE IF NOT EXISTS compose_automation_trigger (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `rel_script` BIGINT(20) UNSIGNED NOT NULL COMMENT 'Script that is triggered',\n\n `resource` VARCHAR(128) NOT NULL COMMENT 'Resource triggering the event',\n `event` VARCHAR(128) NOT NULL COMMENT 'Event triggered',\n `event_condition`\n TEXT NOT NULL COMMENT 'Trigger condition',\n `enabled` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'Trigger enabled?',\n\n `weight` INT NOT NULL DEFAULT 0,\n\n `created_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `updated_at` DATETIME NULL DEFAULT NULL,\n `deleted_by` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,\n `deleted_at` DATETIME NULL DEFAULT NULL,\n\n CONSTRAINT `fk_script` FOREIGN KEY (`rel_script`) REFERENCES `compose_automation_script` (`id`),\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n\n\n# Migrate old triggers into scripts\nINSERT INTO compose_automation_script (id, name, source, source_ref, run_in_ua, critical, enabled, created_at, updated_at, deleted_at)\nSELECT id, name, source, '', true, false, enabled, created_at, updated_at, deleted_at from compose_trigger;\n\n# Migrate old triggers into new triggers\nINSERT INTO compose_automation_trigger (id, event, resource, event_condition, rel_script, enabled, created_at, updated_at, deleted_at)\nSELECT id+seq, events.event, 'compose:record', rel_module, id, enabled, created_at, updated_at, deleted_at from compose_trigger AS t INNER JOIN\n ( SELECT 0 as seq, '' AS event\n UNION SELECT 1 as seq, 'manual' AS event\n UNION SELECT 2 as seq, 'beforeCreate' AS event\n UNION SELECT 3 as seq, 'afterCreate' AS event\n UNION SELECT 4 as seq, 'beforeUpdate' AS event\n UNION SELECT 5 as seq, 'afterUpdate' AS event\n UNION SELECT 6 as seq, 'beforeDelete' AS event\n UNION SELECT 7 as seq, 'afterDelete' AS event) AS events ON ((event = '' AND t.actions = '')\n OR (event <> '' AND t.actions LIKE concat('%',event,'%') ));\n# Normalize and cleanup\nUPDATE compose_automation_trigger SET event = 'manual' WHERE event = '';\nDELETE FROM compose_automation_trigger WHERE event_condition IN ('', '0') AND event <> 'manual';\n\nDROP TABLE IF EXISTS compose_trigger;\nPK\x07\x08c\xda\x17\xa4\x13\x11\x00\x00\x13\x11\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 \x0020190825090000.automation-namespace.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `compose_automation_script`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nUPDATE `compose_automation_script` SET `rel_namespace` = (SELECT MIN(id) FROM compose_namespace);\n\nALTER TABLE `compose_automation_script`\n ADD CONSTRAINT `compose_automation_script_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\nPK\x07\x08;#~I\x98\x01\x00\x00\x98\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 \x0020190912125228.field-default.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `compose_module_field`\n ADD `default_value` JSON DEFAULT NULL COMMENT 'Default value as a record value set.'\n AFTER `options`;\nPK\x07\x08&~D\xee\x8d\x00\x00\x00\x8d\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 \x0020190917080000.add-handles.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `compose_module` ADD `handle` VARCHAR(200) NOT NULL AFTER `id`;\nALTER TABLE `compose_page` ADD `handle` VARCHAR(200) NOT NULL AFTER `id`;\nALTER TABLE `compose_chart` ADD `handle` VARCHAR(200) NOT NULL AFTER `id`;\nPK\x07\x08}h\xa5\xba\xe4\x00\x00\x00\xe4\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!(\xfc\xdb\xfd\xdde \x00\x00e \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\xe25\x00\x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb1(\xbb\xf0\x8d\x05\x00\x00\x8d\x05\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81o7\x00\x0020190514090000.module_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\xd8\xe5H\x12\x01\x00\x00\x12\x01\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81V=\x00\x0020190526090000.permissions.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(c\xda\x17\xa4\x13\x11\x00\x00\x13\x11\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc0>\x00\x0020190701090000.automation.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(;#~I\x98\x01\x00\x00\x98\x01\x00\x00*\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81*P\x00\x0020190825090000.automation-namespace.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(&~D\xee\x8d\x00\x00\x00\x8d\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81#R\x00\x0020190912125228.field-default.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(}h\xa5\xba\xe4\x00\x00\x00\xe4\x00\x00\x00!\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\nS\x00\x0020190917080000.add-handles.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\x81FT\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\x03V\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x1d\x00\x1d\x00\x9f\n\x00\x00oV\x00\x00\x00\x00" diff --git a/compose/db/schema/mysql/20190917080000.add-handles.up.sql b/compose/db/schema/mysql/20190917080000.add-handles.up.sql new file mode 100644 index 000000000..25ebf50bc --- /dev/null +++ b/compose/db/schema/mysql/20190917080000.add-handles.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE `compose_module` ADD `handle` VARCHAR(200) NOT NULL AFTER `id`; +ALTER TABLE `compose_page` ADD `handle` VARCHAR(200) NOT NULL AFTER `id`; +ALTER TABLE `compose_chart` ADD `handle` VARCHAR(200) NOT NULL AFTER `id`; diff --git a/compose/repository/chart.go b/compose/repository/chart.go index 38783c0aa..6ed0423f1 100644 --- a/compose/repository/chart.go +++ b/compose/repository/chart.go @@ -15,6 +15,7 @@ type ( With(ctx context.Context, db *factory.DB) ChartRepository FindByID(namespaceID, chartID uint64) (*types.Chart, error) + FindByHandle(namespaceID uint64, handle string) (c *types.Chart, err 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) @@ -27,7 +28,8 @@ type ( ) const ( - ErrChartNotFound = repositoryError("ChartNotFound") + ErrChartNotFound = repositoryError("ChartNotFound") + ErrChartHandleNotUnique = repositoryError("ChartHandleNotUnique") ) func Chart(ctx context.Context, db *factory.DB) ChartRepository { @@ -46,7 +48,7 @@ func (r chart) table() string { func (r chart) columns() []string { return []string{ - "id", "rel_namespace", "name", "config", + "id", "rel_namespace", "handle", "name", "config", "created_at", "updated_at", "deleted_at", } } @@ -74,6 +76,22 @@ func (r chart) FindByID(namespaceID, chartID uint64) (*types.Chart, error) { return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrChartNotFound) } +func (r chart) FindByHandle(namespaceID uint64, handle string) (*types.Chart, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("handle = ?", handle) + + 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 diff --git a/compose/repository/module.go b/compose/repository/module.go index b725bbb6b..69bf8bb37 100644 --- a/compose/repository/module.go +++ b/compose/repository/module.go @@ -19,6 +19,8 @@ type ( With(ctx context.Context, db *factory.DB) ModuleRepository FindByID(namespaceID, moduleID uint64) (*types.Module, error) + FindByName(namespaceID uint64, name string) (*types.Module, error) + FindByHandle(namespaceID uint64, handle string) (*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) @@ -32,8 +34,9 @@ type ( ) const ( - ErrModuleNotFound = repositoryError("ModuleNotFound") - ErrModuleNameNotUnique = repositoryError("ModuleNameNotUnique") + ErrModuleNotFound = repositoryError("ModuleNotFound") + ErrModuleNameNotUnique = repositoryError("ModuleNameNotUnique") + ErrModuleHandleNotUnique = repositoryError("ModuleHandleNotUnique") ) func Module(ctx context.Context, db *factory.DB) ModuleRepository { @@ -56,7 +59,7 @@ func (r module) tableFields() string { func (r module) columns() []string { return []string{ - "id", "rel_namespace", "name", "json", + "id", "rel_namespace", "handle", "name", "json", "created_at", "updated_at", "deleted_at", } } @@ -70,19 +73,32 @@ func (r module) query() squirrel.SelectBuilder { } func (r module) FindByID(namespaceID, moduleID uint64) (*types.Module, error) { - var ( - query = r.query(). - Columns(r.columns()...). - Where("id = ?", moduleID) + return r.findOneBy(namespaceID, "id", moduleID) +} - c = &types.Module{} +func (r module) FindByHandle(namespaceID uint64, handle string) (*types.Module, error) { + return r.findOneBy(namespaceID, "LOWER(handle)", strings.ToLower(strings.TrimSpace(handle))) +} + +func (r module) FindByName(namespaceID uint64, name string) (*types.Module, error) { + return r.findOneBy(namespaceID, "LOWER(name)", strings.ToLower(strings.TrimSpace(name))) +} + +func (r module) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Module, error) { + var m = &types.Module{} + + err := r.findOneInNamespaceBy( + namespaceID, + r.query().Columns(r.columns()...), + squirrel.Eq{field: value}, + m, ) - if namespaceID > 0 { - query = query.Where("rel_namespace = ?", namespaceID) + if err == nil && m.ID == 0 { + return nil, ErrModuleNotFound } - return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrModuleNotFound) + return m, nil } func (r module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) { @@ -118,10 +134,6 @@ func (r module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.Mo func (r module) Create(mod *types.Module) (*types.Module, error) { var err error - if err = r.checkName(mod.NamespaceID, 0, mod.Name); err != nil { - return nil, err - } - mod.ID = factory.Sonyflake.NextID() mod.CreatedAt = time.Now().Truncate(time.Second) @@ -137,10 +149,6 @@ func (r module) Create(mod *types.Module) (*types.Module, error) { } func (r module) Update(mod *types.Module) (*types.Module, error) { - if err := r.checkName(mod.NamespaceID, 0, mod.Name); err != nil { - return nil, err - } - now := time.Now().Truncate(time.Second) mod.UpdatedAt = &now @@ -245,22 +253,3 @@ func (r module) FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err er return ff, r.db().Select(&ff, sql, args...) } } - -// Checks if there is another module in the namespace with the same name -func (r module) checkName(namespaceID, moduleID uint64, name string) error { - mm, f, err := r.Find(types.ModuleFilter{ - NamespaceID: namespaceID, - Name: name, - PerPage: 1, - }) - - if err != nil { - return err - } - - if f.Count == 0 || mm.FindByID(moduleID) == nil { - return nil - } - - return ErrModuleNameNotUnique -} diff --git a/compose/repository/namespace.go b/compose/repository/namespace.go index d8e657b03..06eaded18 100644 --- a/compose/repository/namespace.go +++ b/compose/repository/namespace.go @@ -28,7 +28,9 @@ type ( ) const ( - ErrNamespaceNotFound = repositoryError("NamespaceNotFound") + ErrNamespaceNotFound = repositoryError("NamespaceNotFound") + ErrNamespaceSlugNotUnique = repositoryError("NamespaceSlugNotUnique") + ErrNamespaceInvalidSlugFormat = repositoryError("NamespaceInvalidSlugFormat") ) func Namespace(ctx context.Context, db *factory.DB) NamespaceRepository { diff --git a/compose/repository/page.go b/compose/repository/page.go index 83042a247..c899bcd61 100644 --- a/compose/repository/page.go +++ b/compose/repository/page.go @@ -15,6 +15,7 @@ type ( With(ctx context.Context, db *factory.DB) PageRepository FindByID(namespaceID, pageID uint64) (*types.Page, error) + FindByHandle(namespaceID uint64, handle string) (*types.Page, error) FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) @@ -31,7 +32,8 @@ type ( ) const ( - ErrPageNotFound = repositoryError("PageNotFound") + ErrPageNotFound = repositoryError("PageNotFound") + ErrPageHandleNotUnique = repositoryError("PageHandleNotUnique") ) func Page(ctx context.Context, db *factory.DB) PageRepository { @@ -50,7 +52,8 @@ func (r page) table() string { func (r page) columns() []string { return []string{ - "id", "rel_namespace", "self_id", "rel_module", "title", + "id", "rel_namespace", "self_id", "rel_module", + "handle", "title", "blocks", "description", "visible", "weight", "created_at", "updated_at", "deleted_at", } @@ -79,6 +82,22 @@ func (r page) FindByID(namespaceID, pageID uint64) (*types.Page, error) { return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrPageNotFound) } +func (r page) FindByHandle(namespaceID uint64, handle string) (*types.Page, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("handle = ?", handle) + + c = &types.Page{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) + } + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrPageNotFound) +} + func (r page) FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) { var ( query = r.query(). diff --git a/compose/repository/repository.go b/compose/repository/repository.go index 85679423a..2d5fbd679 100644 --- a/compose/repository/repository.go +++ b/compose/repository/repository.go @@ -48,6 +48,21 @@ func (r *repository) db() *factory.DB { return DB(r.ctx) } +func (r repository) findOneInNamespaceBy(namespaceID uint64, q squirrel.SelectBuilder, eq squirrel.Eq, row interface{}) error { + q = q.Where(eq) + + if namespaceID > 0 { + q = q.Where("rel_namespace = ?", namespaceID) + } + + if err := r.fetchOne(row, q); err != nil { + row = nil + return err + } + + return nil +} + // Fetches single row from table func (r repository) fetchOne(one interface{}, q squirrel.SelectBuilder) (err error) { return rh.FetchOne(r.db(), q, one) diff --git a/compose/service/chart.go b/compose/service/chart.go index 7e6971eb0..a40aa427f 100644 --- a/compose/service/chart.go +++ b/compose/service/chart.go @@ -8,6 +8,7 @@ import ( "github.com/cortezaproject/corteza-server/compose/repository" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/handle" ) type ( @@ -34,6 +35,7 @@ type ( With(ctx context.Context) ChartService FindByID(namespaceID, chartID uint64) (*types.Chart, error) + FindByHandle(namespaceID uint64, handle string) (*types.Chart, error) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) Create(chart *types.Chart) (*types.Chart, error) @@ -69,17 +71,29 @@ func (svc chart) With(ctx context.Context) ChartService { // } 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 { + if _, err = svc.loadNamespace(namespaceID); err != nil { return + } else { + return svc.checkPermissions(svc.chartRepo.FindByID(namespaceID, chartID)) + } +} + +func (svc chart) FindByHandle(namespaceID uint64, handle string) (c *types.Chart, err error) { + if _, err = svc.loadNamespace(namespaceID); err != nil { + return + } else { + return svc.checkPermissions(svc.chartRepo.FindByHandle(namespaceID, handle)) + } +} + +func (svc chart) checkPermissions(c *types.Chart, err error) (*types.Chart, error) { + if err != nil { + return nil, err } else if !svc.ac.CanReadChart(svc.ctx, c) { return nil, ErrNoReadPermissions.withStack() } - return + return c, err } func (svc chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) { @@ -96,16 +110,28 @@ func (svc chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.Cha } func (svc chart) Create(mod *types.Chart) (c *types.Chart, err error) { + if !handle.IsValid(mod.Handle) { + return nil, ErrInvalidHandle + } + if ns, err := svc.loadNamespace(mod.NamespaceID); err != nil { return nil, err } else if !svc.ac.CanCreateChart(svc.ctx, ns) { return nil, ErrNoCreatePermissions.withStack() } + if err = svc.UniqueCheck(mod); err != nil { + return + } + return svc.chartRepo.Create(mod) } func (svc chart) Update(mod *types.Chart) (c *types.Chart, err error) { + if !handle.IsValid(mod.Handle) { + return nil, ErrInvalidHandle + } + if mod.ID == 0 { return nil, ErrInvalidID.withStack() } @@ -118,6 +144,10 @@ func (svc chart) Update(mod *types.Chart) (c *types.Chart, err error) { return nil, ErrStaleData.withStack() } + if err = svc.UniqueCheck(mod); err != nil { + return + } + if !svc.ac.CanUpdateChart(svc.ctx, c) { return nil, ErrNoUpdatePermissions.withStack() } @@ -146,6 +176,16 @@ func (svc chart) DeleteByID(namespaceID, chartID uint64) error { return svc.chartRepo.DeleteByID(namespaceID, chartID) } +func (svc chart) UniqueCheck(c *types.Chart) (err error) { + if c.Handle != "" { + if e, _ := svc.chartRepo.FindByHandle(c.NamespaceID, c.Handle); e != nil && e.ID != c.ID { + return repository.ErrChartHandleNotUnique + } + } + + return nil +} + func (svc chart) loadNamespace(namespaceID uint64) (ns *types.Namespace, err error) { if namespaceID == 0 { return nil, ErrNamespaceRequired.withStack() diff --git a/compose/service/error.go b/compose/service/error.go index 258e767cd..e2c5e09de 100644 --- a/compose/service/error.go +++ b/compose/service/error.go @@ -10,6 +10,7 @@ type ( const ( ErrInvalidID serviceError = "InvalidID" + ErrInvalidHandle serviceError = "InvalidHandle" ErrStaleData serviceError = "StaleData" ErrNoPermissions serviceError = "NoPermissions" ErrNoGrantPermissions serviceError = "NoGrantPermissions" diff --git a/compose/service/module.go b/compose/service/module.go index 2f7ac7393..28bdc1320 100644 --- a/compose/service/module.go +++ b/compose/service/module.go @@ -8,6 +8,7 @@ import ( "github.com/cortezaproject/corteza-server/compose/repository" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/handle" ) type ( @@ -35,6 +36,8 @@ type ( With(ctx context.Context) ModuleService FindByID(namespaceID, moduleID uint64) (*types.Module, error) + FindByName(namespaceID uint64, name string) (*types.Module, error) + FindByHandle(namespaceID uint64, handle string) (*types.Module, error) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) Create(module *types.Module) (*types.Module, error) @@ -71,19 +74,39 @@ func (svc module) With(ctx context.Context) ModuleService { // } 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 { + if _, err = svc.loadNamespace(namespaceID); err != nil { return + } else { + return svc.loader(svc.moduleRepo.FindByID(namespaceID, moduleID)) + } +} + +func (svc module) FindByName(namespaceID uint64, name string) (m *types.Module, err error) { + if _, err = svc.loadNamespace(namespaceID); err != nil { + return + } else { + return svc.loader(svc.moduleRepo.FindByName(namespaceID, name)) + } +} + +func (svc module) FindByHandle(namespaceID uint64, handle string) (m *types.Module, err error) { + if _, err = svc.loadNamespace(namespaceID); err != nil { + return + } else { + return svc.loader(svc.moduleRepo.FindByHandle(namespaceID, handle)) + } +} + +func (svc module) loader(m *types.Module, err error) (*types.Module, error) { + if err != nil { + return nil, err } else if !svc.ac.CanReadModule(svc.ctx, m) { return nil, ErrNoReadPermissions.withStack() } var ff types.ModuleFieldSet if ff, err = svc.moduleRepo.FindFields(m.ID); err != nil { - return + return nil, err } else { _ = ff.Walk(func(f *types.ModuleField) error { m.Fields = append(m.Fields, f) @@ -91,7 +114,7 @@ func (svc module) FindByID(namespaceID, moduleID uint64) (m *types.Module, err e }) } - return + return m, err } func (svc module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) { @@ -119,11 +142,17 @@ func (svc module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types. } func (svc module) Create(mod *types.Module) (*types.Module, error) { - + if !handle.IsValid(mod.Handle) { + return nil, ErrInvalidHandle + } if mod.NamespaceID == 0 { return nil, ErrNamespaceRequired.withStack() } + if err := svc.UniqueCheck(mod); err != nil { + return nil, err + } + if ns, err := svc.loadNamespace(mod.NamespaceID); err != nil { return nil, err } else if !svc.ac.CanCreateModule(svc.ctx, ns) { @@ -138,10 +167,18 @@ func (svc module) Update(mod *types.Module) (m *types.Module, err error) { return nil, ErrInvalidID.withStack() } + if !handle.IsValid(mod.Handle) { + return nil, ErrInvalidHandle + } + if m, err = svc.moduleRepo.FindByID(mod.NamespaceID, mod.ID); err != nil { return } + if err = svc.UniqueCheck(mod); err != nil { + return + } + if isStale(mod.UpdatedAt, m.UpdatedAt, m.CreatedAt) { return nil, ErrStaleData.withStack() } @@ -175,6 +212,22 @@ func (svc module) DeleteByID(namespaceID, moduleID uint64) error { return svc.moduleRepo.DeleteByID(namespaceID, moduleID) } +func (svc module) UniqueCheck(m *types.Module) (err error) { + if m.Handle != "" { + if e, _ := svc.moduleRepo.FindByHandle(m.NamespaceID, m.Handle); e != nil && e.ID > 0 && e.ID != m.ID { + return repository.ErrModuleHandleNotUnique + } + } + + if m.Name != "" { + if e, _ := svc.moduleRepo.FindByName(m.NamespaceID, m.Name); e != nil && e.ID > 0 && e.ID != m.ID { + return repository.ErrModuleNameNotUnique + } + } + + return nil +} + func (svc module) loadNamespace(namespaceID uint64) (ns *types.Namespace, err error) { if namespaceID == 0 { return nil, ErrNamespaceRequired.withStack() diff --git a/compose/service/namespace.go b/compose/service/namespace.go index df984f1dd..0c8d286b9 100644 --- a/compose/service/namespace.go +++ b/compose/service/namespace.go @@ -3,12 +3,14 @@ package service import ( "context" + "github.com/davecgh/go-spew/spew" "github.com/titpetric/factory" "go.uber.org/zap" "github.com/cortezaproject/corteza-server/compose/repository" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/internal/permissions" + "github.com/cortezaproject/corteza-server/pkg/handle" ) type ( @@ -35,6 +37,7 @@ type ( With(ctx context.Context) NamespaceService FindByID(namespaceID uint64) (*types.Namespace, error) + FindByHandle(handle string) (*types.Namespace, error) Find(types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) Create(namespace *types.Namespace) (*types.Namespace, error) @@ -69,27 +72,25 @@ func (svc namespace) With(ctx context.Context) NamespaceService { // } func (svc namespace) FindByID(ID uint64) (ns *types.Namespace, err error) { - if ID == 0 { - return nil, ErrInvalidID.withStack() - } + return svc.checkPermissions(svc.namespaceRepo.FindByID(ID)) +} - if ns, err = svc.namespaceRepo.FindByID(ID); err != nil { - return - } else if !svc.ac.CanReadNamespace(svc.ctx, ns) { - return nil, ErrNoReadPermissions.withStack() - } - - return +func (svc namespace) FindByHandle(handle string) (ns *types.Namespace, err error) { + return svc.checkPermissions(svc.namespaceRepo.FindBySlug(handle)) } func (svc namespace) FindBySlug(slug string) (ns *types.Namespace, err error) { - if ns, err = svc.namespaceRepo.FindBySlug(slug); err != nil { - return - } else if !svc.ac.CanReadNamespace(svc.ctx, ns) { + return svc.checkPermissions(svc.namespaceRepo.FindBySlug(slug)) +} + +func (svc namespace) checkPermissions(p *types.Namespace, err error) (*types.Namespace, error) { + if err != nil { + return nil, err + } else if !svc.ac.CanReadNamespace(svc.ctx, p) { return nil, ErrNoReadPermissions.withStack() } - return + return p, err } func (svc namespace) Find(filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error) { @@ -107,6 +108,11 @@ func (svc namespace) Find(filter types.NamespaceFilter) (set types.NamespaceSet, // Create adds namespace and presets access rules for role everyone func (svc namespace) Create(mod *types.Namespace) (*types.Namespace, error) { + if !handle.IsValid(mod.Slug) { + spew.Dump(mod.Slug) + return nil, ErrInvalidHandle + } + if !svc.ac.CanCreateNamespace(svc.ctx) { return nil, ErrNoCreatePermissions.withStack() } @@ -119,6 +125,11 @@ func (svc namespace) Update(mod *types.Namespace) (ns *types.Namespace, err erro return nil, ErrInvalidID.withStack() } + if !handle.IsValid(mod.Slug) { + spew.Dump(mod.Slug) + return nil, ErrInvalidHandle + } + ns, err = svc.FindByID(mod.ID) if err != nil { return nil, err @@ -153,3 +164,13 @@ func (svc namespace) DeleteByID(namespaceID uint64) error { return svc.namespaceRepo.DeleteByID(namespaceID) } + +func (svc namespace) UniqueCheck(ns *types.Namespace) (err error) { + if ns.Slug != "" { + if e, _ := svc.namespaceRepo.FindBySlug(ns.Slug); e != nil && e.ID != ns.ID { + return repository.ErrNamespaceSlugNotUnique + } + } + + return nil +} diff --git a/compose/service/page.go b/compose/service/page.go index 39f861a44..b6a2bad8b 100644 --- a/compose/service/page.go +++ b/compose/service/page.go @@ -3,12 +3,12 @@ package service import ( "context" - "github.com/pkg/errors" "github.com/titpetric/factory" "go.uber.org/zap" "github.com/cortezaproject/corteza-server/compose/repository" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/handle" ) type ( @@ -36,6 +36,7 @@ type ( With(ctx context.Context) PageService FindByID(namespaceID, pageID uint64) (*types.Page, error) + FindByHandle(namespaceID uint64, handle string) (*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) @@ -77,18 +78,34 @@ func (svc page) With(ctx context.Context) PageService { // } func (svc page) FindByID(namespaceID, pageID uint64) (p *types.Page, err error) { - return svc.checkPermissions(svc.pageRepo.FindByID(namespaceID, pageID)) + if _, err = svc.loadNamespace(namespaceID); err != nil { + return + } else { + return svc.checkPermissions(svc.pageRepo.FindByID(namespaceID, pageID)) + } +} + +func (svc page) FindByHandle(namespaceID uint64, handle string) (c *types.Page, err error) { + if _, err = svc.loadNamespace(namespaceID); err != nil { + return + } else { + return svc.checkPermissions(svc.pageRepo.FindByHandle(namespaceID, handle)) + } } func (svc page) FindByModuleID(namespaceID, moduleID uint64) (p *types.Page, err error) { - return svc.checkPermissions(svc.pageRepo.FindByModuleID(namespaceID, moduleID)) + if _, err = svc.loadNamespace(namespaceID); err != nil { + return + } else { + return svc.checkPermissions(svc.pageRepo.FindByModuleID(namespaceID, moduleID)) + } } func (svc page) checkPermissions(p *types.Page, err error) (*types.Page, error) { if err != nil { return nil, err } else if !svc.ac.CanReadPage(svc.ctx, p) { - return nil, errors.New("not allowed to access this page") + return nil, ErrNoReadPermissions.withStack() } return p, err @@ -190,13 +207,17 @@ func (svc page) Reorder(namespaceID, selfID uint64, pageIDs []uint64) error { func (svc page) Create(mod *types.Page) (p *types.Page, err error) { mod.ID = 0 + if !handle.IsValid(mod.Handle) { + return nil, ErrInvalidHandle + } + if ns, err := svc.loadNamespace(mod.NamespaceID); err != nil { return nil, err } else if !svc.ac.CanCreatePage(svc.ctx, ns) { return nil, ErrNoCreatePermissions.withStack() } - if err = svc.checkModulePage(mod); err != nil { + if err = svc.UniqueCheck(mod); err != nil { return } @@ -209,6 +230,10 @@ func (svc page) Update(mod *types.Page) (p *types.Page, err error) { return nil, ErrInvalidID.withStack() } + if !handle.IsValid(mod.Handle) { + return nil, ErrInvalidHandle + } + if _, err = svc.loadNamespace(mod.NamespaceID); err != nil { return } @@ -225,7 +250,7 @@ func (svc page) Update(mod *types.Page) (p *types.Page, err error) { return nil, ErrNoUpdatePermissions.withStack() } - if err = svc.checkModulePage(mod); err != nil { + if err = svc.UniqueCheck(mod); err != nil { return } @@ -241,13 +266,15 @@ func (svc page) Update(mod *types.Page) (p *types.Page, err error) { 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 { +func (svc page) UniqueCheck(p *types.Page) (err error) { + if p.Handle != "" { + if e, _ := svc.pageRepo.FindByHandle(p.NamespaceID, p.Handle); e != nil && e.ID != p.ID { + return repository.ErrPageHandleNotUnique + } + } + + if p.ModuleID > 0 { + if p, _ := svc.pageRepo.FindByModuleID(p.NamespaceID, p.ModuleID); p.ID > 0 && p.ID != p.ID { return ErrModulePageExists } } diff --git a/compose/types/namespace.go b/compose/types/namespace.go index 6fd7732fd..7a8a25aa0 100644 --- a/compose/types/namespace.go +++ b/compose/types/namespace.go @@ -3,8 +3,9 @@ package types import ( "time" - "github.com/cortezaproject/corteza-server/internal/permissions" "github.com/jmoiron/sqlx/types" + + "github.com/cortezaproject/corteza-server/internal/permissions" ) type ( diff --git a/system/service/error.go b/system/service/error.go index 3d0fe8717..1751dd2bb 100644 --- a/system/service/error.go +++ b/system/service/error.go @@ -10,6 +10,7 @@ type ( const ( ErrInvalidID serviceError = "InvalidID" + ErrInvalidHandle serviceError = "InvalidHandle" ErrNoPermissions serviceError = "NoPermissions" ErrNoGrantPermissions serviceError = "NoGrantPermissions" ErrNoCreatePermissions serviceError = "NoCreatePermissions" diff --git a/system/service/role.go b/system/service/role.go index f6a72b8c3..b2ed76fa6 100644 --- a/system/service/role.go +++ b/system/service/role.go @@ -7,6 +7,7 @@ import ( "github.com/titpetric/factory" "go.uber.org/zap" + "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/system/repository" "github.com/cortezaproject/corteza-server/system/types" ) @@ -127,6 +128,11 @@ func (svc role) FindByHandle(handle string) (*types.Role, error) { } func (svc role) Create(mod *types.Role) (t *types.Role, err error) { + + if !handle.IsValid(mod.Handle) { + return nil, ErrInvalidHandle + } + if !svc.ac.CanCreateRole(svc.ctx) { return nil, ErrNoCreatePermissions.withStack() } @@ -146,6 +152,10 @@ func (svc role) Update(mod *types.Role) (t *types.Role, err error) { return nil, ErrInvalidID } + if !handle.IsValid(mod.Handle) { + return nil, ErrInvalidHandle + } + if !svc.ac.CanUpdateRole(svc.ctx, mod) { return nil, ErrNoUpdatePermissions.withStack() } @@ -174,36 +184,15 @@ func (svc role) Update(mod *types.Role) (t *types.Role, err error) { } func (svc role) UniqueCheck(r *types.Role) (err error) { - var ( - e *types.Role - - checks = []struct { - query string - find func(string) (*types.Role, error) - err error - }{ - // Checking scenario: - // if email/username/handle is found on another user, error is thrown - {r.Name, svc.FindByName, ErrRoleNameNotUnique}, - {r.Handle, svc.FindByHandle, ErrRoleHandleNotUnique}, + if r.Handle != "" { + if ex, _ := svc.role.FindByHandle(r.Handle); ex.ID > 0 && ex.ID != r.ID { + return ErrRoleHandleNotUnique } - ) + } - for _, c := range checks { - if c.query == "" { - // Skip empty values - continue - } - - e, err = c.find(c.query) - if err == repository.ErrRoleNotFound { - // User not found, proceed to next check - continue - } - - if e.ID > 0 && e.ID != r.ID { - // User found, throw configured error - return c.err + if r.Name != "" { + if ex, _ := svc.role.FindByName(r.Name); ex.ID > 0 && ex.ID != r.ID { + return ErrRoleNameNotUnique } } diff --git a/system/service/user.go b/system/service/user.go index 15e350f87..cb4bd267b 100644 --- a/system/service/user.go +++ b/system/service/user.go @@ -196,37 +196,21 @@ func (svc user) Update(mod *types.User) (u *types.User, err error) { } func (svc user) UniqueCheck(u *types.User) (err error) { - var ( - e *types.User - - checks = []struct { - query string - find func(string) (*types.User, error) - err error - }{ - // Checking scenario: - // if email/username/handle is found on another user, error is thrown - {u.Email, svc.FindByEmail, ErrUserEmailNotUnique}, - {u.Username, svc.FindByUsername, ErrUserUsernameNotUnique}, - {u.Handle, svc.FindByHandle, ErrUserHandleNotUnique}, + if u.Email != "" { + if ex, _ := svc.user.FindByEmail(u.Email); ex.ID > 0 && ex.ID != u.ID { + return ErrUserEmailNotUnique } - ) + } - for _, c := range checks { - if c.query == "" { - // Skip empty values - continue + if u.Username != "" { + if ex, _ := svc.user.FindByUsername(u.Username); ex.ID > 0 && ex.ID != u.ID { + return ErrUserUsernameNotUnique } + } - e, err = c.find(c.query) - if err == repository.ErrUserNotFound { - // User not found, proceed to next check - continue - } - - if e.ID > 0 && e.ID != u.ID { - // User found, throw configured error - return c.err + if u.Handle != "" { + if ex, _ := svc.user.FindByHandle(u.Handle); ex.ID > 0 && ex.ID != u.ID { + return ErrUserHandleNotUnique } } diff --git a/system/system.go b/system/system.go index 6353c648f..11a3de94c 100644 --- a/system/system.go +++ b/system/system.go @@ -123,6 +123,9 @@ func Configure() *cli.Config { func(ctx context.Context, c *cli.Config) *cobra.Command { return commands.Auth(ctx, c) }, + func(ctx context.Context, c *cli.Config) *cobra.Command { + return commands.Importer(ctx, c) + }, func(ctx context.Context, c *cli.Config) *cobra.Command { return commands.Users(ctx, c) }, diff --git a/tests/compose/chart_test.go b/tests/compose/chart_test.go index e617229bc..96fb93bc9 100644 --- a/tests/compose/chart_test.go +++ b/tests/compose/chart_test.go @@ -9,6 +9,7 @@ import ( jsonpath "github.com/steinfletcher/apitest-jsonpath" "github.com/cortezaproject/corteza-server/compose/repository" + "github.com/cortezaproject/corteza-server/compose/service" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/tests/helpers" ) @@ -20,7 +21,7 @@ func (h helper) repoChart() repository.ChartRepository { func (h helper) repoMakeChart(ns *types.Namespace, name string) *types.Chart { m, err := h. repoChart(). - Create(&types.Chart{Name: name, NamespaceID: ns.ID}) + Create(&types.Chart{Name: name, Handle: name, NamespaceID: ns.ID}) h.a.NoError(err) return m @@ -44,6 +45,22 @@ func TestChartRead(t *testing.T) { End() } +func TestChartReadByHandle(t *testing.T) { + h := newHelper(t) + + h.allow(types.NamespacePermissionResource.AppendWildcard(), "read") + h.allow(types.ChartPermissionResource.AppendWildcard(), "read") + ns := h.repoMakeNamespace("some-namespace") + c := h.repoMakeChart(ns, "some-chart") + + cbh, err := service.DefaultChart.With(h.secCtx()).FindByHandle(ns.ID, c.Handle) + + h.a.NoError(err) + h.a.NotNil(cbh) + h.a.Equal(cbh.ID, c.ID) + h.a.Equal(cbh.Handle, c.Handle) +} + func TestChartList(t *testing.T) { h := newHelper(t) diff --git a/tests/compose/module_test.go b/tests/compose/module_test.go index 349b2733d..ea79bc789 100644 --- a/tests/compose/module_test.go +++ b/tests/compose/module_test.go @@ -9,6 +9,7 @@ import ( jsonpath "github.com/steinfletcher/apitest-jsonpath" "github.com/cortezaproject/corteza-server/compose/repository" + "github.com/cortezaproject/corteza-server/compose/service" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/tests/helpers" ) @@ -44,6 +45,22 @@ func TestModuleRead(t *testing.T) { End() } +func TestModuleReadByHandle(t *testing.T) { + h := newHelper(t) + + h.allow(types.NamespacePermissionResource.AppendWildcard(), "read") + h.allow(types.ModulePermissionResource.AppendWildcard(), "read") + ns := h.repoMakeNamespace("some-namespace") + c := h.repoMakeModule(ns, "some-module") + + cbh, err := service.DefaultModule.With(h.secCtx()).FindByHandle(ns.ID, c.Handle) + + h.a.NoError(err) + h.a.NotNil(cbh) + h.a.Equal(cbh.ID, c.ID) + h.a.Equal(cbh.Handle, c.Handle) +} + func TestModuleList(t *testing.T) { h := newHelper(t) diff --git a/tests/compose/namespace_test.go b/tests/compose/namespace_test.go index da409cea5..ad8405a56 100644 --- a/tests/compose/namespace_test.go +++ b/tests/compose/namespace_test.go @@ -9,7 +9,9 @@ import ( jsonpath "github.com/steinfletcher/apitest-jsonpath" "github.com/cortezaproject/corteza-server/compose/repository" + "github.com/cortezaproject/corteza-server/compose/service" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/internal/rand" "github.com/cortezaproject/corteza-server/tests/helpers" ) @@ -20,7 +22,7 @@ func (h helper) repoNamespace() repository.NamespaceRepository { func (h helper) repoMakeNamespace(name string) *types.Namespace { ns, err := h. repoNamespace(). - Create(&types.Namespace{Name: name}) + Create(&types.Namespace{Name: name, Slug: name}) h.a.NoError(err) return ns @@ -41,6 +43,21 @@ func TestNamespaceRead(t *testing.T) { End() } +func TestNamespaceReadByHandle(t *testing.T) { + h := newHelper(t) + + h.allow(types.NamespacePermissionResource.AppendWildcard(), "read") + h.allow(types.NamespacePermissionResource.AppendWildcard(), "read") + ns := h.repoMakeNamespace("some-namespace-" + string(rand.Bytes(20))) + + nsbh, err := service.DefaultNamespace.With(h.secCtx()).FindByHandle(ns.Slug) + + h.a.NoError(err) + h.a.NotNil(nsbh) + h.a.Equal(nsbh.ID, ns.ID) + h.a.Equal(nsbh.Slug, ns.Slug) +} + func TestNamespaceList(t *testing.T) { h := newHelper(t) diff --git a/tests/compose/page_test.go b/tests/compose/page_test.go index 26c575d3a..9a9da7562 100644 --- a/tests/compose/page_test.go +++ b/tests/compose/page_test.go @@ -9,6 +9,7 @@ import ( jsonpath "github.com/steinfletcher/apitest-jsonpath" "github.com/cortezaproject/corteza-server/compose/repository" + "github.com/cortezaproject/corteza-server/compose/service" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/tests/helpers" ) @@ -44,6 +45,22 @@ func TestPageRead(t *testing.T) { End() } +func TestPageReadByHandle(t *testing.T) { + h := newHelper(t) + + h.allow(types.NamespacePermissionResource.AppendWildcard(), "read") + h.allow(types.PagePermissionResource.AppendWildcard(), "read") + ns := h.repoMakeNamespace("some-namespace") + c := h.repoMakePage(ns, "some-page") + + cbh, err := service.DefaultPage.With(h.secCtx()).FindByHandle(ns.ID, c.Handle) + + h.a.NoError(err) + h.a.NotNil(cbh) + h.a.Equal(cbh.ID, c.ID) + h.a.Equal(cbh.Handle, c.Handle) +} + func TestPageList(t *testing.T) { h := newHelper(t) diff --git a/tests/system/main_test.go b/tests/system/main_test.go index 9bb9eb6dc..a955b4481 100644 --- a/tests/system/main_test.go +++ b/tests/system/main_test.go @@ -43,6 +43,16 @@ var ( p = permissions.NewTestService() ) +// random string, 10 chars long by default +func rs(a ...int) string { + var l = 10 + if len(a) > 0 { + l = a[0] + } + + return string(rand.Bytes(l)) +} + func db() *factory.DB { return factory.Database.MustGet("system").With(context.Background()) } diff --git a/tests/system/role_test.go b/tests/system/role_test.go index 8839dfb52..80a48094e 100644 --- a/tests/system/role_test.go +++ b/tests/system/role_test.go @@ -6,9 +6,9 @@ import ( "net/http" "testing" + "github.com/davecgh/go-spew/spew" jsonpath "github.com/steinfletcher/apitest-jsonpath" - "github.com/cortezaproject/corteza-server/internal/rand" "github.com/cortezaproject/corteza-server/system/repository" "github.com/cortezaproject/corteza-server/system/types" "github.com/cortezaproject/corteza-server/tests/helpers" @@ -18,19 +18,32 @@ func (h helper) repoRole() repository.RoleRepository { return repository.Role(context.Background(), db()) } -func (h helper) repoMakeRole(name string) *types.Role { - u, err := h. +func (h helper) repoMakeRole(ss ...string) *types.Role { + var r = &types.Role{} + if len(ss) > 1 { + r.Handle = ss[1] + } else { + r.Handle = rs() + + } + if len(ss) > 0 { + r.Name = ss[0] + } else { + r.Name = rs() + } + + r, err := h. repoRole(). - Create(&types.Role{Name: name}) + Create(r) h.a.NoError(err) - return u + return r } func TestRoleRead(t *testing.T) { h := newHelper(t) - u := h.repoMakeRole(string(rand.Bytes(10))) + u := h.repoMakeRole() h.apiInit(). Get(fmt.Sprintf("/roles/%d", u.ID)). @@ -61,21 +74,47 @@ func TestRoleCreateForbidden(t *testing.T) { h.apiInit(). Post("/roles/"). - FormData("name", string(rand.Bytes(10))). + FormData("name", rs()). Expect(t). Status(http.StatusOK). Assert(helpers.AssertError("system.service.NoCreatePermissions")). End() } +func TestRoleCreateNotUnique(t *testing.T) { + h := newHelper(t) + h.allow(types.SystemPermissionResource, "role.create") + + role := h.repoMakeRole() + spew.Dump(role) + h.apiInit(). + Post("/roles/"). + FormData("name", rs()). + FormData("handle", role.Handle). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("system.service.RoleHandleNotUnique")). + End() + + h.apiInit(). + Post("/roles/"). + FormData("name", role.Name). + FormData("handle", rs()). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("system.service.RoleNameNotUnique")). + End() + +} + func TestRoleCreate(t *testing.T) { h := newHelper(t) h.allow(types.SystemPermissionResource, "role.create") h.apiInit(). Post("/roles/"). - FormData("name", string(rand.Bytes(10))). - FormData("handle", string(rand.Bytes(10))). + FormData("name", rs()). + FormData("handle", rs()). Expect(t). Status(http.StatusOK). Assert(helpers.AssertNoErrors). @@ -84,7 +123,7 @@ func TestRoleCreate(t *testing.T) { func TestRoleUpdateForbidden(t *testing.T) { h := newHelper(t) - u := h.repoMakeRole(string(rand.Bytes(10))) + u := h.repoMakeRole() h.apiInit(). Put(fmt.Sprintf("/roles/%d", u.ID)). @@ -97,11 +136,11 @@ func TestRoleUpdateForbidden(t *testing.T) { func TestRoleUpdate(t *testing.T) { h := newHelper(t) - u := h.repoMakeRole(string(rand.Bytes(10))) + u := h.repoMakeRole() h.allow(types.RolePermissionResource.AppendWildcard(), "update") - newName := "updated-" + string(rand.Bytes(10)) - newHandle := "updated-" + string(rand.Bytes(10)) + newName := "updated-" + rs() + newHandle := "updated-" + rs() h.apiInit(). Put(fmt.Sprintf("/roles/%d", u.ID)). @@ -121,7 +160,7 @@ func TestRoleUpdate(t *testing.T) { func TestRoleDeleteForbidden(t *testing.T) { h := newHelper(t) - u := h.repoMakeRole(string(rand.Bytes(10))) + u := h.repoMakeRole() h.apiInit(). Delete(fmt.Sprintf("/roles/%d", u.ID)). @@ -135,7 +174,7 @@ func TestRoleDelete(t *testing.T) { h := newHelper(t) h.allow(types.RolePermissionResource.AppendWildcard(), "delete") - u := h.repoMakeRole(string(rand.Bytes(10))) + u := h.repoMakeRole() h.apiInit(). Delete(fmt.Sprintf("/roles/%d", u.ID)). diff --git a/tests/system/user_test.go b/tests/system/user_test.go index 6e98087dd..92dc5ea0e 100644 --- a/tests/system/user_test.go +++ b/tests/system/user_test.go @@ -7,7 +7,6 @@ import ( "testing" jsonpath "github.com/steinfletcher/apitest-jsonpath" - "github.com/titpetric/factory" "github.com/cortezaproject/corteza-server/system/repository" "github.com/cortezaproject/corteza-server/system/types" @@ -15,7 +14,7 @@ import ( ) func (h helper) randEmail() string { - return fmt.Sprintf("%d@test.tld", factory.Sonyflake.NextID()) + return fmt.Sprintf("%s@test.tld", rs()) } func (h helper) repoUser() repository.UserRepository {