From edee0543929cd16259ee588296fb97ec35c0bdfd Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Sun, 28 Apr 2019 15:28:03 +0200 Subject: [PATCH] Apply namespace changes to all layers of pages --- api/compose/spec.json | 18 +++ api/compose/spec/page.json | 18 +++ compose/db/mysql/static.go | 2 +- .../20190428080000.page-timestamps.up.sql | 6 + compose/internal/repository/chart.go | 10 +- compose/internal/repository/module.go | 6 +- compose/internal/repository/page.go | 147 +++++++++++++----- compose/internal/repository/trigger.go | 10 +- compose/internal/service/attachment.go | 4 +- compose/internal/service/page.go | 74 +++++---- compose/internal/service/page_test.go | 8 - compose/rest/chart.go | 38 ++--- compose/rest/page.go | 123 +++++++++++---- compose/rest/record.go | 1 - compose/rest/request/page.go | 15 ++ compose/types/page.go | 18 ++- docs/compose/README.md | 3 + 17 files changed, 347 insertions(+), 154 deletions(-) create mode 100644 compose/db/schema/mysql/20190428080000.page-timestamps.up.sql diff --git a/api/compose/spec.json b/api/compose/spec.json index c34e4037d..4761c1600 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -219,6 +219,24 @@ "name": "selfID", "required": false, "title": "Parent page ID" + }, + { + "type": "string", + "name": "query", + "required": false, + "title": "Search query" + }, + { + "name": "page", + "type": "uint", + "required": false, + "title": "Page number (0 based)" + }, + { + "name": "perPage", + "type": "uint", + "required": false, + "title": "Returned items per page (default 50)" } ] } diff --git a/api/compose/spec/page.json b/api/compose/spec/page.json index 9f70840c7..ab63344c9 100644 --- a/api/compose/spec/page.json +++ b/api/compose/spec/page.json @@ -35,6 +35,24 @@ "required": false, "title": "Parent page ID", "type": "uint64" + }, + { + "name": "query", + "required": false, + "title": "Search query", + "type": "string" + }, + { + "name": "page", + "required": false, + "title": "Page number (0 based)", + "type": "uint" + }, + { + "name": "perPage", + "required": false, + "title": "Returned items per page (default 50)", + "type": "uint" } ] } diff --git a/compose/db/mysql/static.go b/compose/db/mysql/static.go index f0543a7b4..915d95ddb 100644 --- a/compose/db/mysql/static.go +++ b/compose/db/mysql/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package mysql -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO compose_namespace (id, name, slug, enabled, meta) VALUES (88714882739863655, 'Crust CRM', 'crm', true, '{}');\n\nALTER TABLE `compose_attachment`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_chart`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_module`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_page`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_trigger`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record_value`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `record_id`,\n ADD INDEX (`rel_namespace`);\n\nUPDATE `compose_attachment` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_chart` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_module` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_page` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_trigger` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record_value` SET `rel_namespace` = 88714882739863655;\n\n\nALTER TABLE `compose_attachment`\n ADD CONSTRAINT `compose_attachment_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_chart`\n ADD CONSTRAINT `compose_chart_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_module`\n ADD CONSTRAINT `compose_module_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_page`\n ADD CONSTRAINT `compose_page_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record`\n ADD CONSTRAINT `compose_record_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_trigger`\n ADD CONSTRAINT `compose_trigger_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record_value`\n ADD CONSTRAINT `compose_record_value_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\nPK\x07\x08\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81v)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\",\x00\x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81`7\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\x1d9\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x16\x00\x16\x00'\x08\x00\x00\x899\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO compose_namespace (id, name, slug, enabled, meta) VALUES (88714882739863655, 'Crust CRM', 'crm', true, '{}');\n\nALTER TABLE `compose_attachment`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_chart`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_module`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_page`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_trigger`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,\n ADD INDEX (`rel_namespace`);\n\nALTER TABLE `compose_record_value`\n ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `record_id`,\n ADD INDEX (`rel_namespace`);\n\nUPDATE `compose_attachment` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_chart` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_module` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_page` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_trigger` SET `rel_namespace` = 88714882739863655;\nUPDATE `compose_record_value` SET `rel_namespace` = 88714882739863655;\n\n\nALTER TABLE `compose_attachment`\n ADD CONSTRAINT `compose_attachment_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_chart`\n ADD CONSTRAINT `compose_chart_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_module`\n ADD CONSTRAINT `compose_module_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_page`\n ADD CONSTRAINT `compose_page_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record`\n ADD CONSTRAINT `compose_record_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_trigger`\n ADD CONSTRAINT `compose_trigger_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\n\nALTER TABLE `compose_record_value`\n ADD CONSTRAINT `compose_record_value_namespace`\n FOREIGN KEY (`rel_namespace`)\n REFERENCES `compose_namespace` (`id`);\nPK\x07\x08\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `compose_page`\n ADD COLUMN `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n ADD COLUMN `updated_at` DATETIME DEFAULT NULL,\n ADD COLUMN `deleted_at` DATETIME DEFAULT NULL;\nPK\x07\x08\xbds\x93}\xca\x00\x00\x00\xca\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xf2\x1a)|\x97\x02\x00\x00\x97\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81v)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd7\xb5!\xe1\xe3\n\x00\x00\xe3\n\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\",\x00\x0020190428080000.namespace-refs.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xbds\x93}\xca\x00\x00\x00\xca\x00\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81`7\x00\x0020190428080000.page-timestamps.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x868\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81C:\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x17\x00\x17\x00\x83\x08\x00\x00\xaf:\x00\x00\x00\x00" diff --git a/compose/db/schema/mysql/20190428080000.page-timestamps.up.sql b/compose/db/schema/mysql/20190428080000.page-timestamps.up.sql new file mode 100644 index 000000000..e4e43432f --- /dev/null +++ b/compose/db/schema/mysql/20190428080000.page-timestamps.up.sql @@ -0,0 +1,6 @@ +ALTER TABLE `compose_page` + ADD COLUMN `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + ADD COLUMN `updated_at` DATETIME DEFAULT NULL, + ADD COLUMN `deleted_at` DATETIME DEFAULT NULL; + +ALTER TABLE `compose_page` CHANGE COLUMN `module_id` `rel_module` BIGINT UNSIGNED NOT NULL DEFAULT 0; diff --git a/compose/internal/repository/chart.go b/compose/internal/repository/chart.go index 00dfcf63a..e068426a0 100644 --- a/compose/internal/repository/chart.go +++ b/compose/internal/repository/chart.go @@ -14,11 +14,11 @@ type ( ChartRepository interface { With(ctx context.Context, db *factory.DB) ChartRepository - FindByID(namespaceID, attachmentID uint64) (*types.Chart, error) + FindByID(namespaceID, chartID uint64) (*types.Chart, error) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) Create(mod *types.Chart) (*types.Chart, error) Update(mod *types.Chart) (*types.Chart, error) - DeleteByID(namespaceID, attachmentID uint64) error + DeleteByID(namespaceID, chartID uint64) error } chart struct { @@ -81,7 +81,7 @@ func (r chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.Chart query := r.query() if filter.NamespaceID > 0 { - query = query.Where("a.rel_namespace = ?", filter.NamespaceID) + query = query.Where("rel_namespace = ?", filter.NamespaceID) } if f.Query != "" { @@ -113,11 +113,11 @@ func (r chart) Update(mod *types.Chart) (*types.Chart, error) { return mod, r.db().Replace(r.table(), mod) } -func (r chart) DeleteByID(namespaceID, attachmentID uint64) error { +func (r chart) DeleteByID(namespaceID, chartID uint64) error { _, err := r.db().Exec( "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", namespaceID, - attachmentID, + chartID, ) return err diff --git a/compose/internal/repository/module.go b/compose/internal/repository/module.go index 1d377e126..117d4b2f1 100644 --- a/compose/internal/repository/module.go +++ b/compose/internal/repository/module.go @@ -85,7 +85,7 @@ func (r module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.Mo query := r.query() if filter.NamespaceID > 0 { - query = query.Where("a.rel_namespace = ?", filter.NamespaceID) + query = query.Where("rel_namespace = ?", filter.NamespaceID) } if f.Query != "" { @@ -143,11 +143,11 @@ func (r module) updateFields(moduleID uint64, ff types.ModuleFieldSet) error { return nil } -func (r module) DeleteByID(namespaceID, attachmentID uint64) error { +func (r module) DeleteByID(namespaceID, moduleID uint64) error { _, err := r.db().Exec( "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", namespaceID, - attachmentID, + moduleID, ) return err diff --git a/compose/internal/repository/page.go b/compose/internal/repository/page.go index bf06d2ae3..341f99a0a 100644 --- a/compose/internal/repository/page.go +++ b/compose/internal/repository/page.go @@ -2,8 +2,10 @@ package repository import ( "context" + "time" "github.com/titpetric/factory" + "gopkg.in/Masterminds/squirrel.v1" "github.com/crusttech/crust/compose/types" ) @@ -12,17 +14,15 @@ type ( PageRepository interface { With(ctx context.Context, db *factory.DB) PageRepository - FindByID(id uint64) (*types.Page, error) - FindByModuleID(id uint64) (*types.Page, error) - FindBySelfID(selfID uint64) (types.PageSet, error) - Find() (types.PageSet, error) - FindRecordPages() (types.PageSet, error) + FindByID(namespaceID, pageID uint64) (*types.Page, error) + FindByModuleID(namespaceID, pageID uint64) (*types.Page, error) + Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) Create(mod *types.Page) (*types.Page, error) Update(mod *types.Page) (*types.Page, error) - DeleteByID(id uint64) error + DeleteByID(namespaceID, pageID uint64) error - Reorder(selfID uint64, pageIDs []uint64) error + Reorder(namespaceID, selfID uint64, pageIDs []uint64) error } page struct { @@ -30,51 +30,108 @@ type ( } ) +const ( + ErrPageNotFound = repositoryError("PageNotFound") +) + func Page(ctx context.Context, db *factory.DB) PageRepository { return (&page{}).With(ctx, db) } -func (r *page) With(ctx context.Context, db *factory.DB) PageRepository { +func (r page) With(ctx context.Context, db *factory.DB) PageRepository { return &page{ repository: r.repository.With(ctx, db), } } -func (r *page) FindByID(id uint64) (*types.Page, error) { - page := &types.Page{} - if err := r.db().Get(page, "SELECT * FROM compose_page WHERE id=?", id); err != nil { - return page, err +func (r page) table() string { + return "compose_page" +} + +func (r page) columns() []string { + return []string{ + "id", "rel_namespace", "self_id", "rel_module", "title", + "blocks", "description", "visible", "weight", + "created_at", "updated_at", "deleted_at", } - return page, nil } -func (r *page) FindByModuleID(id uint64) (*types.Page, error) { - page := &types.Page{} - if err := r.db().Get(page, "SELECT * FROM compose_page WHERE module_id=?", id); err != nil { - return nil, err +func (r page) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table()). + Where("deleted_at IS NULL") +} + +func (r page) FindByID(namespaceID, pageID uint64) (*types.Page, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("id = ?", pageID) + + c = &types.Page{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) } - return page, nil + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrPageNotFound) } -func (r *page) FindRecordPages() (set types.PageSet, err error) { - return set, r.db().Select(&set, "SELECT * FROM compose_page WHERE module_id > 0") -} +func (r page) FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) { + var ( + query = r.query(). + Columns(r.columns()...). + Where("rel_module = ?", moduleID) -func (r *page) FindBySelfID(selfID uint64) (types.PageSet, error) { - pages := types.PageSet{} - if err := r.db().Select(&pages, "SELECT * FROM compose_page WHERE self_id = ? ORDER BY weight ASC", selfID); err != nil { - return pages, err + c = &types.Page{} + ) + + if namespaceID > 0 { + query = query.Where("rel_namespace = ?", namespaceID) } - return pages, nil + + return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrPageNotFound) } -func (r *page) Find() (set types.PageSet, err error) { - return set, r.db().Select(&set, "SELECT * FROM compose_page ORDER BY self_id, weight ASC") +func (r page) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) { + f = filter + f.PerPage = normalizePerPage(f.PerPage, 5, 100, 50) + + query := r.query() + + if filter.NamespaceID > 0 { + query = query.Where("rel_namespace = ?", filter.NamespaceID) + } + + if filter.ParentID > 0 { + query = query.Where("self_id = ?", filter.ParentID) + } + + if f.Query != "" { + q := "%" + f.Query + "%" + query = query.Where("title LIKE ? OR description LIKE ?", q, q) + } + + if f.Count, err = r.count(query); err != nil || f.Count == 0 { + return + } + + query = query. + Columns(r.columns()...). + OrderBy("weight ASC") + + return set, f, r.fetchPaged(&set, query, f.Page, f.PerPage) } -func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { - pageMap := map[uint64]bool{} - if pages, err := r.FindBySelfID(selfID); err != nil { +func (r page) Reorder(namespaceID, parentID uint64, pageIDs []uint64) error { + var ( + pageMap = map[uint64]bool{} + filter = types.PageFilter{NamespaceID: namespaceID, ParentID: parentID} + ) + + if pages, _, err := r.Find(filter); err != nil { return nil } else { for _, page := range pages { @@ -87,7 +144,7 @@ func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { for _, pageID := range pageIDs { if pageMap[pageID] { pageMap[pageID] = false - if _, err := db.Exec("UPDATE compose_page set weight=? where id=? and self_id=?", weight, pageID, selfID); err != nil { + if _, err := db.Exec("UPDATE compose_page SET weight = ? WHERE id = ? AND self_id = ?", weight, pageID, parentID); err != nil { return err } weight++ @@ -95,7 +152,7 @@ func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { } for pageID, update := range pageMap { if update { - if _, err := db.Exec("UPDATE compose_page set weight=? where id=? and self_id=?", weight, pageID, selfID); err != nil { + if _, err := db.Exec("UPDATE compose_page SET weight = ? WHERE id = ? AND self_id = ?", weight, pageID, parentID); err != nil { return err } weight++ @@ -104,19 +161,25 @@ func (r *page) Reorder(selfID uint64, pageIDs []uint64) error { return nil } -func (r *page) Create(item *types.Page) (*types.Page, error) { - page := &types.Page{} - *page = *item +func (r page) Create(mod *types.Page) (*types.Page, error) { + mod.ID = factory.Sonyflake.NextID() + mod.CreatedAt = time.Now() - page.ID = factory.Sonyflake.NextID() - return page, r.db().Insert("compose_page", page) + return mod, r.db().Insert(r.table(), mod) } -func (r *page) Update(page *types.Page) (*types.Page, error) { - return page, r.db().Replace("compose_page", page) +func (r page) Update(mod *types.Page) (*types.Page, error) { + now := time.Now() + mod.UpdatedAt = &now + return mod, r.db().Replace(r.table(), mod) } -func (r *page) DeleteByID(id uint64) error { - _, err := r.db().Exec("DELETE FROM compose_page WHERE id=?", id) +func (r page) DeleteByID(namespaceID, pageID uint64) error { + _, err := r.db().Exec( + "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", + namespaceID, + pageID, + ) + return err } diff --git a/compose/internal/repository/trigger.go b/compose/internal/repository/trigger.go index cf3588776..045094d5d 100644 --- a/compose/internal/repository/trigger.go +++ b/compose/internal/repository/trigger.go @@ -14,11 +14,11 @@ type ( TriggerRepository interface { With(ctx context.Context, db *factory.DB) TriggerRepository - FindByID(namespaceID, attachmentID uint64) (*types.Trigger, error) + FindByID(namespaceID, triggerID uint64) (*types.Trigger, error) Find(filter types.TriggerFilter) (set types.TriggerSet, f types.TriggerFilter, err error) Create(mod *types.Trigger) (*types.Trigger, error) Update(mod *types.Trigger) (*types.Trigger, error) - DeleteByID(namespaceID, attachmentID uint64) error + DeleteByID(namespaceID, triggerID uint64) error } trigger struct { @@ -82,7 +82,7 @@ func (r trigger) Find(filter types.TriggerFilter) (set types.TriggerSet, f types query := r.query() if filter.NamespaceID > 0 { - query = query.Where("a.rel_namespace = ?", filter.NamespaceID) + query = query.Where("rel_namespace = ?", filter.NamespaceID) } if f.Query != "" { @@ -114,11 +114,11 @@ func (r trigger) Update(mod *types.Trigger) (*types.Trigger, error) { return mod, r.db().Replace(r.table(), mod) } -func (r trigger) DeleteByID(namespaceID, attachmentID uint64) error { +func (r trigger) DeleteByID(namespaceID, triggerID uint64) error { _, err := r.db().Exec( "UPDATE "+r.table()+" SET deleted_at = NOW() WHERE rel_namespace = ? AND id = ?", namespaceID, - attachmentID, + triggerID, ) return err diff --git a/compose/internal/service/attachment.go b/compose/internal/service/attachment.go index 0d522fb9f..010cfa3a3 100644 --- a/compose/internal/service/attachment.go +++ b/compose/internal/service/attachment.go @@ -108,7 +108,7 @@ func (svc attachment) Find(filter types.AttachmentFilter) (types.AttachmentSet, } if filter.PageID > 0 { - if _, err := svc.pageSvc.FindByID(filter.PageID); err != nil { + if _, err := svc.pageSvc.FindByID(filter.NamespaceID, filter.PageID); err != nil { return nil, filter, err } } @@ -151,7 +151,7 @@ func (svc attachment) CreatePageAttachment(namespaceID uint64, name string, size var currentUserID uint64 = auth.GetIdentityFromContext(svc.ctx).Identity() - if p, err := svc.pageSvc.FindByID(pageID); err != nil { + if p, err := svc.pageSvc.FindByID(namespaceID, pageID); err != nil { return nil, err } else if !svc.prmSvc.CanUpdatePage(p) { return nil, errors.New("not allowed to add attachments to this page") diff --git a/compose/internal/service/page.go b/compose/internal/service/page.go index 9e6869218..579d84094 100644 --- a/compose/internal/service/page.go +++ b/compose/internal/service/page.go @@ -24,18 +24,17 @@ type ( PageService interface { With(ctx context.Context) PageService - FindByID(pageID uint64) (*types.Page, error) - FindByModuleID(moduleID uint64) (*types.Page, error) - FindBySelfID(selfID uint64) (pages types.PageSet, err error) - Find() (pages types.PageSet, err error) - Tree() (pages types.PageSet, err error) - FindRecordPages() (pages types.PageSet, err error) + FindByID(namespaceID, pageID uint64) (*types.Page, error) + FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) + FindBySelfID(namespaceID, selfID uint64) (pages types.PageSet, f types.PageFilter, err error) + Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) + Tree(namespaceID uint64) (pages types.PageSet, err error) Create(page *types.Page) (*types.Page, error) Update(page *types.Page) (*types.Page, error) - DeleteByID(pageID uint64) error + DeleteByID(namespaceID, pageID uint64) error - Reorder(selfID uint64, pageIDs []uint64) error + Reorder(namespaceID, selfID uint64, pageIDs []uint64) error } ) @@ -58,12 +57,12 @@ func (svc *page) With(ctx context.Context) PageService { } } -func (svc *page) FindByID(id uint64) (p *types.Page, err error) { - return svc.checkPermissions(svc.pageRepo.FindByID(id)) +func (svc *page) FindByID(namespaceID, pageID uint64) (p *types.Page, err error) { + return svc.checkPermissions(svc.pageRepo.FindByID(namespaceID, pageID)) } -func (svc *page) FindByModuleID(moduleID uint64) (p *types.Page, err error) { - return svc.checkPermissions(svc.pageRepo.FindByModuleID(moduleID)) +func (svc *page) FindByModuleID(namespaceID, moduleID uint64) (p *types.Page, err error) { + return svc.checkPermissions(svc.pageRepo.FindByModuleID(namespaceID, moduleID)) } func (svc *page) checkPermissions(p *types.Page, err error) (*types.Page, error) { @@ -76,19 +75,27 @@ func (svc *page) checkPermissions(p *types.Page, err error) (*types.Page, error) return p, err } -func (svc *page) FindBySelfID(selfID uint64) (pp types.PageSet, err error) { - return svc.filterPageSet(svc.pageRepo.FindBySelfID(selfID)) +func (svc *page) FindBySelfID(namespaceID, parentID uint64) (pp types.PageSet, f types.PageFilter, err error) { + return svc.filterPageSetByPermission(svc.pageRepo.Find(types.PageFilter{ + NamespaceID: namespaceID, + ParentID: parentID, + })) } -func (svc *page) Find() (pages types.PageSet, err error) { - return svc.filterPageSet(svc.pageRepo.Find()) +func (svc *page) Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error) { + return svc.filterPageSetByPermission(svc.pageRepo.Find(filter)) } -func (svc *page) Tree() (pages types.PageSet, err error) { - var tree types.PageSet +func (svc *page) Tree(namespaceID uint64) (pages types.PageSet, err error) { + var ( + tree types.PageSet + filter = types.PageFilter{ + NamespaceID: namespaceID, + } + ) return tree, svc.db.Transaction(func() (err error) { - if pages, err = svc.filterPageSet(svc.pageRepo.Find()); err != nil { + if pages, _, err = svc.filterPageSetByPermission(svc.pageRepo.Find(filter)); err != nil { return } @@ -116,22 +123,21 @@ func (svc *page) Tree() (pages types.PageSet, err error) { }) } -func (svc *page) FindRecordPages() (pages types.PageSet, err error) { - return svc.pageRepo.FindRecordPages() -} - -func (svc *page) filterPageSet(pp types.PageSet, err error) (types.PageSet, error) { +func (svc *page) filterPageSetByPermission(pp types.PageSet, f types.PageFilter, err error) (types.PageSet, types.PageFilter, error) { if err != nil { - return nil, err + return nil, f, err } - return pp.Filter(func(m *types.Page) (bool, error) { + // @todo Filter-by-permission can/will mess up filter's count & paging... + pp, err = pp.Filter(func(m *types.Page) (bool, error) { return svc.prmSvc.CanReadPage(m), nil }) + + return pp, f, err } -func (svc *page) Reorder(selfID uint64, pageIDs []uint64) error { - return svc.pageRepo.Reorder(selfID, pageIDs) +func (svc *page) Reorder(namespaceID, selfID uint64, pageIDs []uint64) error { + return svc.pageRepo.Reorder(namespaceID, selfID, pageIDs) } func (svc *page) Create(page *types.Page) (p *types.Page, err error) { @@ -141,7 +147,7 @@ func (svc *page) Create(page *types.Page) (p *types.Page, err error) { } if page.ModuleID > 0 { - if p, err = svc.pageRepo.FindByModuleID(page.ModuleID); err != nil { + if p, err = svc.pageRepo.FindByModuleID(page.NamespaceID, page.ModuleID); err != nil { return err } else if p.ID > 0 { return errors.New("Page for module already exists") @@ -162,7 +168,7 @@ func (svc *page) Update(page *types.Page) (p *types.Page, err error) { validate := func() error { if page.ID == 0 { return errors.New("Error when saving page, invalid ID") - } else if p, err = svc.pageRepo.FindByID(page.ID); err != nil { + } else if p, err = svc.pageRepo.FindByID(page.NamespaceID, page.ID); err != nil { return errors.Wrap(err, "Error while loading page for update") } else { if !svc.prmSvc.CanUpdatePage(p) { @@ -171,7 +177,7 @@ func (svc *page) Update(page *types.Page) (p *types.Page, err error) { } if page.ModuleID > 0 { - if p, err = svc.pageRepo.FindByModuleID(page.ModuleID); err != nil { + if p, err = svc.pageRepo.FindByModuleID(page.NamespaceID, page.ModuleID); err != nil { return err } else if p.ID > 0 && page.ID != p.ID { return errors.New("Page for module already exists") @@ -188,12 +194,12 @@ func (svc *page) Update(page *types.Page) (p *types.Page, err error) { }) } -func (svc *page) DeleteByID(ID uint64) error { - if p, err := svc.pageRepo.FindByID(ID); err != nil { +func (svc *page) DeleteByID(namespaceID, pageID uint64) error { + if p, err := svc.pageRepo.FindByID(namespaceID, pageID); err != nil { return errors.Wrap(err, "could not delete page") } else if !svc.prmSvc.CanDeletePage(p) { return errors.New("not allowed to delete this page") } - return svc.pageRepo.DeleteByID(ID) + return svc.pageRepo.DeleteByID(namespaceID, pageID) } diff --git a/compose/internal/service/page_test.go b/compose/internal/service/page_test.go index 6e4acddcf..0547b0033 100644 --- a/compose/internal/service/page_test.go +++ b/compose/internal/service/page_test.go @@ -106,14 +106,6 @@ func TestPage(t *testing.T) { prevPageCount = len(ms) } - // fetch all record pages - { - ms, err := repository.FindRecordPages() - test.Assert(t, err == nil, "Error when retrieving pages: %+v", err) - test.Assert(t, len(ms) >= 1, "Expected at least one page, got %d", len(ms)) - prevPageCount = len(ms) - } - // fetch all pages { ms, err := repository.FindBySelfID(m.ID) diff --git a/compose/rest/chart.go b/compose/rest/chart.go index 65457b9e6..c351a2d13 100644 --- a/compose/rest/chart.go +++ b/compose/rest/chart.go @@ -53,14 +53,14 @@ func (ctrl Chart) List(ctx context.Context, r *request.ChartList) (interface{}, func (ctrl Chart) Create(ctx context.Context, r *request.ChartCreate) (interface{}, error) { var err error - ns := &types.Chart{ + mod := &types.Chart{ NamespaceID: r.NamespaceID, Name: r.Name, Config: r.Config, } - ns, err = ctrl.chart.With(ctx).Create(ns) - return ctrl.makePayload(ctx, ns, err) + mod, err = ctrl.chart.With(ctx).Create(mod) + return ctrl.makePayload(ctx, mod, err) } func (ctrl Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, error) { @@ -69,18 +69,18 @@ func (ctrl Chart) Read(ctx context.Context, r *request.ChartRead) (interface{}, func (ctrl Chart) Update(ctx context.Context, r *request.ChartUpdate) (interface{}, error) { var ( - ns = &types.Chart{} + mod = &types.Chart{} err error ) - ns.ID = r.ChartID - ns.Name = r.Name - ns.Config = r.Config - ns.NamespaceID = r.NamespaceID - ns.UpdatedAt = r.UpdatedAt + mod.ID = r.ChartID + mod.Name = r.Name + mod.Config = r.Config + mod.NamespaceID = r.NamespaceID + mod.UpdatedAt = r.UpdatedAt - ns, err = ctrl.chart.With(ctx).Update(ns) - return ctrl.makePayload(ctx, ns, err) + mod, err = ctrl.chart.With(ctx).Update(mod) + return ctrl.makePayload(ctx, mod, err) } func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface{}, error) { @@ -92,18 +92,18 @@ func (ctrl Chart) Delete(ctx context.Context, r *request.ChartDelete) (interface return resputil.OK(), ctrl.chart.With(ctx).DeleteByID(r.NamespaceID, r.ChartID) } -func (ctrl Chart) makePayload(ctx context.Context, t *types.Chart, err error) (*chartPayload, error) { - if err != nil || t == nil { +func (ctrl Chart) makePayload(ctx context.Context, c *types.Chart, err error) (*chartPayload, error) { + if err != nil || c == nil { return nil, err } perm := ctrl.permissions.With(ctx) return &chartPayload{ - Chart: t, + Chart: c, - CanUpdateChart: perm.CanUpdateChart(t), - CanDeleteChart: perm.CanDeleteChart(t), + CanUpdateChart: perm.CanUpdateChart(c), + CanDeleteChart: perm.CanDeleteChart(c), }, nil } @@ -112,11 +112,11 @@ func (ctrl Chart) makeFilterPayload(ctx context.Context, nn types.ChartSet, f ty return nil, err } - nsp := &chartSetPayload{Filter: f, Set: make([]*chartPayload, len(nn))} + modp := &chartSetPayload{Filter: f, Set: make([]*chartPayload, len(nn))} for i := range nn { - nsp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) } - return nsp, nil + return modp, nil } diff --git a/compose/rest/page.go b/compose/rest/page.go index 8eb346c24..734ec0c32 100644 --- a/compose/rest/page.go +++ b/compose/rest/page.go @@ -12,70 +12,100 @@ import ( ) type ( + pagePayload struct { + *types.Page + + CanUpdatePage bool `json:"canUpdatePage"` + CanDeletePage bool `json:"canDeletePage"` + } + + pageSetPayload struct { + Filter types.PageFilter `json:"filter"` + Set []*pagePayload `json:"set"` + } + Page struct { - page service.PageService - attachment service.AttachmentService + page service.PageService + attachment service.AttachmentService + permissions service.PermissionsService } ) func (Page) New() *Page { return &Page{ - page: service.DefaultPage, - attachment: service.DefaultAttachment, + page: service.DefaultPage, + attachment: service.DefaultAttachment, + permissions: service.DefaultPermissions, } } func (ctrl *Page) List(ctx context.Context, r *request.PageList) (interface{}, error) { - if r.SelfID > 0 { - return ctrl.page.With(ctx).FindBySelfID(r.SelfID) - } else { - return ctrl.page.With(ctx).Find() + f := types.PageFilter{ + NamespaceID: r.NamespaceID, + ParentID: r.SelfID, + + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, } + + set, filter, err := ctrl.page.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, set, filter, err) } func (ctrl *Page) Tree(ctx context.Context, r *request.PageTree) (interface{}, error) { - return ctrl.page.With(ctx).Tree() + return ctrl.page.With(ctx).Tree(r.NamespaceID) } func (ctrl *Page) Create(ctx context.Context, r *request.PageCreate) (interface{}, error) { - p := &types.Page{ - SelfID: r.SelfID, - ModuleID: r.ModuleID, - Title: r.Title, - Description: r.Description, - Blocks: r.Blocks, - Visible: r.Visible, - } - return ctrl.page.With(ctx).Create(p) + var ( + err error + mod = &types.Page{ + NamespaceID: r.NamespaceID, + SelfID: r.SelfID, + ModuleID: r.ModuleID, + Title: r.Title, + Description: r.Description, + Blocks: r.Blocks, + Visible: r.Visible, + } + ) + + mod, err = ctrl.page.With(ctx).Create(mod) + return ctrl.makePayload(ctx, mod, err) } func (ctrl *Page) Read(ctx context.Context, r *request.PageRead) (interface{}, error) { - return ctrl.page.With(ctx).FindByID(r.PageID) + return ctrl.page.With(ctx).FindByID(r.NamespaceID, r.PageID) } func (ctrl *Page) Reorder(ctx context.Context, r *request.PageReorder) (interface{}, error) { - return resputil.OK(), ctrl.page.With(ctx).Reorder(r.SelfID, payload.ParseUInt64s(r.PageIDs)) + return resputil.OK(), ctrl.page.With(ctx).Reorder(r.NamespaceID, r.SelfID, payload.ParseUInt64s(r.PageIDs)) } func (ctrl *Page) Update(ctx context.Context, r *request.PageUpdate) (interface{}, error) { - p := &types.Page{ - ID: r.PageID, - SelfID: r.SelfID, - ModuleID: r.ModuleID, - Title: r.Title, - Description: r.Description, - Blocks: r.Blocks, - Visible: r.Visible, - } - return ctrl.page.With(ctx).Update(p) + var ( + err error + mod = &types.Page{ + ID: r.PageID, + SelfID: r.SelfID, + ModuleID: r.ModuleID, + Title: r.Title, + Description: r.Description, + Blocks: r.Blocks, + Visible: r.Visible, + } + ) + + mod, err = ctrl.page.With(ctx).Update(mod) + return ctrl.makePayload(ctx, mod, err) } func (ctrl *Page) Delete(ctx context.Context, r *request.PageDelete) (interface{}, error) { - return resputil.OK(), ctrl.page.With(ctx).DeleteByID(r.PageID) + return resputil.OK(), ctrl.page.With(ctx).DeleteByID(r.NamespaceID, r.PageID) } func (ctrl *Page) Upload(ctx context.Context, r *request.PageUpload) (interface{}, error) { - // @todo [SECURITY] check if attachments can be added to this page file, err := r.Upload.Open() if err != nil { return nil, err @@ -93,3 +123,32 @@ func (ctrl *Page) Upload(ctx context.Context, r *request.PageUpload) (interface{ return makeAttachmentPayload(ctx, a, err) } + +func (ctrl Page) makePayload(ctx context.Context, c *types.Page, err error) (*pagePayload, error) { + if err != nil || c == nil { + return nil, err + } + + perm := ctrl.permissions.With(ctx) + + return &pagePayload{ + Page: c, + + CanUpdatePage: perm.CanUpdatePage(c), + CanDeletePage: perm.CanDeletePage(c), + }, nil +} + +func (ctrl Page) makeFilterPayload(ctx context.Context, nn types.PageSet, f types.PageFilter, err error) (*pageSetPayload, error) { + if err != nil { + return nil, err + } + + modp := &pageSetPayload{Filter: f, Set: make([]*pagePayload, len(nn))} + + for i := range nn { + modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + } + + return modp, nil +} diff --git a/compose/rest/record.go b/compose/rest/record.go index 13f40e39a..14ce5cc95 100644 --- a/compose/rest/record.go +++ b/compose/rest/record.go @@ -54,7 +54,6 @@ func (ctrl *Record) Delete(ctx context.Context, r *request.RecordDelete) (interf } func (ctrl *Record) Upload(ctx context.Context, r *request.RecordUpload) (interface{}, error) { - // @todo [SECURITY] check if attachments can be added to this page file, err := r.Upload.Open() if err != nil { return nil, err diff --git a/compose/rest/request/page.go b/compose/rest/request/page.go index 15f0e3e95..b9b859e28 100644 --- a/compose/rest/request/page.go +++ b/compose/rest/request/page.go @@ -35,6 +35,9 @@ var _ = multipart.FileHeader{} // Page list request parameters type PageList struct { SelfID uint64 `json:",string"` + Query string + Page uint + PerPage uint NamespaceID uint64 `json:",string"` } @@ -73,6 +76,18 @@ func (pReq *PageList) Fill(r *http.Request) (err error) { pReq.SelfID = parseUInt64(val) } + if val, ok := get["query"]; ok { + + pReq.Query = val + } + if val, ok := get["page"]; ok { + + pReq.Page = parseUint(val) + } + if val, ok := get["perPage"]; ok { + + pReq.PerPage = parseUint(val) + } pReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) return err diff --git a/compose/types/page.go b/compose/types/page.go index b0fdabbb8..0622e49e1 100644 --- a/compose/types/page.go +++ b/compose/types/page.go @@ -1,6 +1,8 @@ package types import ( + "time" + "github.com/jmoiron/sqlx/types" "github.com/crusttech/crust/internal/rules" @@ -14,8 +16,7 @@ type ( NamespaceID uint64 `json:"namespaceID,string" db:"rel_namespace"` - ModuleID uint64 `json:"moduleID,string" db:"module_id"` - Module *Module `json:"module,omitempty" db:"-"` + ModuleID uint64 `json:"moduleID,string" db:"rel_module"` Title string `json:"title" db:"title"` Description string `json:"description" db:"description"` @@ -26,6 +27,10 @@ type ( Visible bool `json:"visible" db:"visible"` Weight int `json:"-" db:"weight"` + + CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"` + UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"` + DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"` } // Block - value of Page.Blocks ([]Block) @@ -39,6 +44,15 @@ type ( Width int `json:"width"` Height int `json:"height"` } + + PageFilter struct { + NamespaceID uint64 `json:"namespaceID,string"` + ParentID uint64 `json:"paentID,string"` + Query string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + Count uint `json:"count"` + } ) // Resource returns a system resource ID for this type diff --git a/docs/compose/README.md b/docs/compose/README.md index 96e5814ee..45ef5f8c5 100644 --- a/docs/compose/README.md +++ b/docs/compose/README.md @@ -461,6 +461,9 @@ Compose pages | Parameter | Type | Method | Description | Default | Required? | | --------- | ---- | ------ | ----------- | ------- | --------- | | selfID | uint64 | GET | Parent page ID | N/A | NO | +| query | string | GET | Search query | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | | namespaceID | uint64 | PATH | Namespace ID | N/A | YES | ## Create page