diff --git a/api/compose/spec.json b/api/compose/spec.json index 0fc5c11eb..54627907f 100644 --- a/api/compose/spec.json +++ b/api/compose/spec.json @@ -24,6 +24,165 @@ } ] }, + { + "title": "Namespaces", + "parameters": {}, + "entrypoint": "namespace", + "path": "/namespace", + "authentication": [], + "struct": [ + { + "imports": [ + "sqlxTypes github.com/jmoiron/sqlx/types", + "time" + ] + } + ], + "apis": [ + { + "name": "list", + "method": "GET", + "title": "List namespaces", + "path": "/", + "parameters": { + "get": [ + { + "type": "string", + "name": "query", + "required": false, + "title": "Search query" + }, + { + "name": "page", + "type": "uint", + "required": false, + "title": "Page number (0 based)" + }, + { + "name": "perPage", + "type": "uint", + "required": false, + "title": "Returned items per page (default 50)" + } + ] + } + }, + { + "name": "create", + "method": "POST", + "title": "Create namespace", + "path": "/", + "parameters": { + "post": [ + { + "type": "string", + "name": "name", + "required": true, + "title": "Name" + }, + { + "type": "string", + "name": "slug", + "required": true, + "title": "Slug (url path part)" + }, + { + "type": "bool", + "name": "enabled", + "required": true, + "title": "Enabled" + }, + { + "type": "sqlxTypes.JSONText", + "name": "meta", + "required": true, + "title": "Meta data" + } + ] + } + }, + { + "name": "read", + "method": "GET", + "title": "Read namespace", + "path": "/{namespaceID}", + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "ID" + } + ] + } + }, + { + "name": "update", + "method": "POST", + "title": "Update namespace", + "path": "/{namespaceID}", + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "ID" + } + ], + "post": [ + { + "type": "string", + "name": "name", + "required": true, + "title": "Name" + }, + { + "type": "string", + "name": "slug", + "required": true, + "title": "Slug (url path part)" + }, + { + "type": "bool", + "name": "enabled", + "required": true, + "title": "Enabled" + }, + { + "type": "sqlxTypes.JSONText", + "name": "meta", + "required": true, + "title": "Meta data" + }, + { + "type": "*time.Time", + "name": "updatedAt", + "required": true, + "title": "Last update (or creation) date" + } + ] + } + }, + { + "name": "delete", + "method": "DELETE", + "title": "Delete namespace", + "path": "/{namespaceID}", + "parameters": { + "path": [ + { + "type": "uint64", + "name": "namespaceID", + "required": true, + "title": "ID" + } + ] + } + } + ] + }, { "title": "Pages", "description": "Compose module pages", @@ -374,7 +533,7 @@ }, { "title": "Records", - "description": "Compose records ", + "description": "Compose records", "entrypoint": "record", "path": "/module/{moduleID}/record", "authentication": [], diff --git a/api/compose/spec/namespace.json b/api/compose/spec/namespace.json new file mode 100644 index 000000000..3b8588e4f --- /dev/null +++ b/api/compose/spec/namespace.json @@ -0,0 +1,160 @@ +{ + "Title": "Namespaces", + "Interface": "Namespace", + "Struct": [ + { + "imports": [ + "sqlxTypes github.com/jmoiron/sqlx/types", + "time" + ] + } + ], + "Parameters": {}, + "Protocol": "", + "Authentication": [], + "Path": "/namespace", + "APIs": [ + { + "Name": "list", + "Method": "GET", + "Title": "List namespaces", + "Path": "/", + "Parameters": { + "get": [ + { + "name": "query", + "required": false, + "title": "Search query", + "type": "string" + }, + { + "name": "page", + "required": false, + "title": "Page number (0 based)", + "type": "uint" + }, + { + "name": "perPage", + "required": false, + "title": "Returned items per page (default 50)", + "type": "uint" + } + ] + } + }, + { + "Name": "create", + "Method": "POST", + "Title": "Create namespace", + "Path": "/", + "Parameters": { + "post": [ + { + "name": "name", + "required": true, + "title": "Name", + "type": "string" + }, + { + "name": "slug", + "required": true, + "title": "Slug (url path part)", + "type": "string" + }, + { + "name": "enabled", + "required": true, + "title": "Enabled", + "type": "bool" + }, + { + "name": "meta", + "required": true, + "title": "Meta data", + "type": "sqlxTypes.JSONText" + } + ] + } + }, + { + "Name": "read", + "Method": "GET", + "Title": "Read namespace", + "Path": "/{namespaceID}", + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "ID", + "type": "uint64" + } + ] + } + }, + { + "Name": "update", + "Method": "POST", + "Title": "Update namespace", + "Path": "/{namespaceID}", + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "ID", + "type": "uint64" + } + ], + "post": [ + { + "name": "name", + "required": true, + "title": "Name", + "type": "string" + }, + { + "name": "slug", + "required": true, + "title": "Slug (url path part)", + "type": "string" + }, + { + "name": "enabled", + "required": true, + "title": "Enabled", + "type": "bool" + }, + { + "name": "meta", + "required": true, + "title": "Meta data", + "type": "sqlxTypes.JSONText" + }, + { + "name": "updatedAt", + "required": true, + "title": "Last update (or creation) date", + "type": "*time.Time" + } + ] + } + }, + { + "Name": "delete", + "Method": "DELETE", + "Title": "Delete namespace", + "Path": "/{namespaceID}", + "Parameters": { + "path": [ + { + "name": "namespaceID", + "required": true, + "title": "ID", + "type": "uint64" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/codegen.sh b/codegen.sh index c03df6a4f..59a1ffab3 100755 --- a/codegen.sh +++ b/codegen.sh @@ -22,6 +22,7 @@ function types { CGO_ENABLED=0 go build -o ./build/gen-type-set codegen/v2/type-set.go fi + ./build/gen-type-set --types Namespace --output compose/types/namespace.gen.go ./build/gen-type-set --types Attachment --output compose/types/attachment.gen.go ./build/gen-type-set --types Module --output compose/types/module.gen.go ./build/gen-type-set --types Page --output compose/types/page.gen.go diff --git a/codegen/codegen.php b/codegen/codegen.php index 9a25eb96e..e0e7c3acb 100755 --- a/codegen/codegen.php +++ b/codegen/codegen.php @@ -101,6 +101,8 @@ $parsers = array( "int" => "parseInt", "uint" => "parseUint", "bool" => "parseBool", + "time.Time" => "parseISODateWithErr", + "*time.Time" => "parseISODatePtrWithErr", "sqlxTypes.JSONText" => "parseJSONTextWithErr", ); diff --git a/compose/db/mysql/static.go b/compose/db/mysql/static.go index fc0c28726..988dd5f27 100644 --- a/compose/db/mysql/static.go +++ b/compose/db/mysql/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package mysql -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_content\n RENAME TO compose_content;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x8a)\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81G+\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x14\x00\x14\x00r\x07\x00\x00\xb3+\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00 \x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content` (\n `id` bigint(20) unsigned NOT NULL,\n `module_id` bigint(20) unsigned NOT NULL,\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`,`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_content_column` (\n `content_id` bigint(20) NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `column_value` text NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_field` (\n `field_type` varchar(16) NOT NULL COMMENT 'Short field type (string, boolean,...)',\n `field_name` varchar(255) NOT NULL COMMENT 'Description of field contents',\n `field_template` varchar(255) NOT NULL COMMENT 'HTML template file for field',\n PRIMARY KEY (`field_type`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module` (\n `id` bigint(20) unsigned NOT NULL,\n `name` varchar(64) NOT NULL COMMENT 'The name of the module',\n `json` json NOT NULL COMMENT 'List of field definitions for the module',\n `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` datetime DEFAULT NULL,\n `deleted_at` datetime DEFAULT NULL,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_module_form` (\n `module_id` bigint(20) unsigned NOT NULL,\n `place` tinyint(3) unsigned NOT NULL,\n `kind` varchar(64) NOT NULL COMMENT 'The type of the form input field',\n `name` varchar(64) NOT NULL COMMENT 'The name of the field in the form',\n `label` varchar(255) NOT NULL COMMENT 'The label of the form input',\n `help_text` text NOT NULL COMMENT 'Help text',\n `default_value` text NOT NULL COMMENT 'Default value',\n `max_length` int(10) unsigned NOT NULL COMMENT 'Maximum input length',\n `is_private` tinyint(1) NOT NULL COMMENT 'Contains personal/sensitive data?',\n PRIMARY KEY (`module_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nCREATE TABLE `crm_page` (\n `id` bigint(20) unsigned NOT NULL COMMENT 'Page ID',\n `self_id` bigint(20) unsigned NOT NULL COMMENT 'Parent Page ID',\n `module_id` bigint(20) unsigned NOT NULL COMMENT 'Module ID (optional)',\n `title` varchar(255) NOT NULL COMMENT 'Title (required)',\n `description` text NOT NULL COMMENT 'Description',\n `blocks` json NOT NULL COMMENT 'JSON array of blocks for the page',\n `visible` tinyint(4) NOT NULL COMMENT 'Is page visible in navigation?',\n `weight` int(11) NOT NULL COMMENT 'Order for navigation',\n PRIMARY KEY (`id`) USING BTREE,\n KEY `module_id` (`module_id`),\n KEY `self_id` (`self_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x08\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` VALUES ('bool','Boolean value (yes / no)','');\nINSERT INTO `crm_field` VALUES ('email','E-mail input','');\nINSERT INTO `crm_field` VALUES ('enum','Single option picker','');\nINSERT INTO `crm_field` VALUES ('hidden','Hidden value','');\nINSERT INTO `crm_field` VALUES ('stamp','Date/time input','');\nINSERT INTO `crm_field` VALUES ('text','Text input','');\nINSERT INTO `crm_field` VALUES ('textarea','Text input (multi-line)','');\nPK\x07\x08f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `user_id` BIGINT UNSIGNED NOT NULL AFTER `module_id`, ADD INDEX (`user_id`);\nPK\x07\x08\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x00 \x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8INSERT INTO `crm_field` (`field_type`, `field_name`, `field_template`) VALUES ('related', 'Related content', ''), ('related_multi', 'Related content (multiple)', '');PK\x07\x08:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_content_links` (\n `content_id` bigint(20) unsigned NOT NULL,\n `column_name` varchar(255) NOT NULL,\n `rel_content_id` bigint(20) unsigned NOT NULL,\n PRIMARY KEY (`content_id`,`column_name`,`rel_content_id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;PK\x07\x08\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00D\x00 \x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `is_required` TINYINT(1) NOT NULL AFTER `is_private`, ADD `is_visible` TINYINT(1) NOT NULL AFTER `is_required`;PK\x07\x08\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x005\x00 \x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` DROP PRIMARY KEY, ADD PRIMARY KEY(`module_id`, `place`);\nPK\x07\x08\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` ADD `json` json DEFAULT NULL COMMENT 'Content in JSON format.' AFTER `user_id`;\nPK\x07\x08\"\x96\xd6pj\x00\x00\x00j\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004\x00 \x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_module_form` ADD `json` JSON NOT NULL COMMENT 'Options in JSON format.' AFTER `kind`;PK\x07\x08\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\x00 \x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_content` RENAME TO `crm_record`;\nALTER TABLE `crm_record` MODIFY COLUMN `json` json DEFAULT NULL COMMENT 'Records in JSON format.';\n\nALTER TABLE `crm_content_column` RENAME TO `crm_record_column`;\nALTER TABLE `crm_record_column` CHANGE COLUMN `content_id` `record_id` bigint(20);\n\nALTER TABLE `crm_content_links` RENAME TO `crm_record_links`;\nALTER TABLE `crm_record_links` CHANGE COLUMN `content_id` `record_id` bigint(20) unsigned;\nALTER TABLE `crm_record_links` CHANGE COLUMN `rel_content_id` `rel_record_id` bigint(20) unsigned;\nPK\x07\x08mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00 \x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_chart` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the chart',\n `config` JSON NOT NULL COMMENT 'Chart & reporting configuration',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE `crm_field`;\nPK\x07\x08\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\x00 \x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `crm_trigger` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'The name of the trigger',\n `enabled` BOOLEAN NOT NULL COMMENT 'Trigger enabled?',\n `actions` TEXT NOT NULL COMMENT 'All actions that trigger it',\n `source` TEXT NOT NULL COMMENT 'Trigger source',\n `rel_module` BIGINT(20) UNSIGNED NULL COMMENT 'Primary module',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\x00 \x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` DROP COLUMN `json`;\nPK\x07\x08\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00 \x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8-- No more links, we'll handle this through ref field on crm_record_value tbl\nDROP TABLE IF EXISTS `crm_record_links`;\n\n-- Not columns, values\nALTER TABLE `crm_record_column` RENAME TO `crm_record_value`;\n\n-- Simplify names\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_name` `name` VARCHAR(64);\nALTER TABLE `crm_record_value` CHANGE COLUMN `column_value` `value` TEXT;\n\n-- Add reference\nALTER TABLE `crm_record_value` ADD COLUMN `ref` BIGINT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `deleted_at` datetime DEFAULT NULL;\nALTER TABLE `crm_record_value` ADD COLUMN `place` INT UNSIGNED DEFAULT 0 NOT NULL;\nALTER TABLE `crm_record_value` DROP PRIMARY KEY, ADD PRIMARY KEY(`record_id`, `name`, `place`);\nCREATE INDEX crm_record_value_ref ON crm_record_value (ref);\n\n\n-- We want this as a real field\nALTER TABLE `crm_module_form` ADD COLUMN `is_multi` TINYINT(1) NOT NULL;\n\n-- This will be handled through meta(json) fieldd\nALTER TABLE `crm_module_form` DROP COLUMN `help_text`;\nALTER TABLE `crm_module_form` DROP COLUMN `max_length`;\nALTER TABLE `crm_module_form` DROP COLUMN `default_Value`;\nPK\x07\x08\x04]{\x1fo\x04\x00\x00o\x04\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8ALTER TABLE `crm_record` CHANGE COLUMN `user_id` `owned_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `updated_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nALTER TABLE `crm_record` ADD COLUMN `deleted_by` BIGINT UNSIGNED NOT NULL DEFAULT 0;\nUPDATE crm_record SET created_by = owned_by;\nUPDATE crm_record SET updated_by = owned_by WHERE updated_at IS NOT NULL;\nUPDATE crm_record SET deleted_by = owned_by WHERE deleted_at IS NOT NULL;\nPK\x07\x08h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00 \x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE crm_attachment (\n id BIGINT UNSIGNED NOT NULL,\n rel_owner BIGINT UNSIGNED NOT NULL,\n\n kind VARCHAR(32) NOT NULL,\n\n url VARCHAR(512),\n preview_url VARCHAR(512),\n\n size INT UNSIGNED,\n mimetype VARCHAR(255),\n name TEXT,\n\n meta JSON,\n\n created_at DATETIME NOT NULL DEFAULT NOW(),\n updated_at DATETIME NULL,\n deleted_at DATETIME NULL,\n\n PRIMARY KEY (id)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- page attachments will be referenced via page-block meta data\n-- module/record attachment will be referenced via crm_record_value\nPK\x07\x08\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\x00 \x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8DROP TABLE IF EXISTS crm_field;\nDROP TABLE IF EXISTS crm_fields;\nDROP TABLE IF EXISTS crm_content_links;\nDROP TABLE IF EXISTS crm_content_column;\nDROP TABLE IF EXISTS crm_module_content;\n\nALTER TABLE crm_attachment\n RENAME TO compose_attachment;\n\nALTER TABLE crm_chart\n RENAME TO compose_chart;\n\nALTER TABLE crm_content\n RENAME TO compose_content;\n\nALTER TABLE crm_module\n RENAME TO compose_module;\n\nALTER TABLE crm_module_form\n RENAME TO compose_module_form;\n\nALTER TABLE crm_page\n RENAME TO compose_page;\n\nALTER TABLE crm_record\n RENAME TO compose_record;\n\nALTER TABLE crm_record_value\n RENAME TO compose_record_value;\n\nALTER TABLE crm_trigger\n RENAME TO compose_trigger;\nPK\x07\x08z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\x00 \x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE `compose_namespace` (\n `id` BIGINT(20) UNSIGNED NOT NULL,\n `name` VARCHAR(64) NOT NULL COMMENT 'Name',\n `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug',\n `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?',\n `meta` JSON NOT NULL COMMENT 'Meta data',\n\n `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` DATETIME DEFAULT NULL,\n `deleted_at` DATETIME DEFAULT NULL,\n\n PRIMARY KEY (`id`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\nPK\x07\x08m\xeb\xed~R\x02\x00\x00R\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00migrations.sqlUT\x05\x00\x01\x80Cm8CREATE TABLE IF NOT EXISTS `migrations` (\n `project` varchar(16) NOT NULL COMMENT 'sam, crm, ...',\n `filename` varchar(255) NOT NULL COMMENT 'yyyymmddHHMMSS.sql',\n `statement_index` int(11) NOT NULL COMMENT 'Statement number from SQL file',\n `status` text NOT NULL COMMENT 'ok or full error message',\n PRIMARY KEY (`project`,`filename`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\nPK\x07\x089S\x05%x\x01\x00\x00x\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00 \x00new.shUT\x05\x00\x01\x80Cm8#!/bin/bash\ntouch $(date +%Y%m%d%H%M%S).up.sql\nPK\x07\x08\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xac\xe8\x19\x1d\x12\n\x00\x00\x12\n\x00\x00\x1a\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x0020180704080000.base.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(f\x18\x1e\x84\xc5\x01\x00\x00\xc5\x01\x00\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81c\n\x00\x0020180704080001.crm_fields-data.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xeb!\x81\xc2k\x00\x00\x00k\x00\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x84\x0c\x00\x0020181109133134.crm_content-ownership.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(:.\xfb8\xa6\x00\x00\x00\xa6\x00\x00\x00.\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81Q\x0d\x00\x0020181109193047.crm_fields-related_types.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xee\x12\x15 \x05\x01\x00\x00\x05\x01\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\\\x0e\x00\x0020181125122152.add_multiple_relationships.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xa5q c\x91\x00\x00\x00\x91\x00\x00\x00D\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc8\x0f\x00\x0020181125132142.add_required_and_visible_to_module_form_fields.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xd9\xd4i\xe3W\x00\x00\x00W\x00\x00\x005\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xd4\x10\x00\x0020181202163130.fix-crm-module-form-primary-key.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\"\x96\xd6pj\x00\x00\x00j\x00\x00\x000\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x97\x11\x00\x0020181204123650.add-crm-content-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xb7\x93\xd4\xf6f\x00\x00\x00f\x00\x00\x004\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81h\x12\x00\x0020181204155326.add-crm-module-form-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(mA\xa8\x1e&\x02\x00\x00&\x02\x00\x00+\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x819\x13\x00\x0020181216214630.crm-content-to-record.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xcf\xc6g\xf6\xe4\x01\x00\x00\xe4\x01\x00\x00$\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xc1\x15\x00\x0020181217100000.add-charts-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae \xfd2\x18\x00\x00\x00\x18\x00\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x18\x00\x0020181224122301.rem-crm_field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(+\xad\xb7\xed\xb8\x02\x00\x00\xb8\x02\x00\x00&\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81r\x18\x00\x0020190108100000.add-triggers-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x94#\xb9\x99-\x00\x00\x00-\x00\x00\x00/\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x87\x1b\x00\x0020190110175924.rem-crm-record-json-field.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\x04]{\x1fo\x04\x00\x00o\x04\x00\x008\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x1a\x1c\x00\x0020190114072000.cleanup-record-tables-and-multival.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(h\xe2\xeb\n!\x02\x00\x00!\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xf8 \x00\x0020190121132408.record-updated-by.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xce\xde?\x08\xb3\x02\x00\x00\xb3\x02\x00\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w#\x00\x0020190227090642.attachment.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(z\x97\x10\xc8\xab\x02\x00\x00\xab\x02\x00\x00'\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x81&\x00\x0020190427180922.change-tbl-prefix.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(m\xeb\xed~R\x02\x00\x00R\x02\x00\x00#\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x8a)\x00\x0020190427210922.namespace-tbl.up.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(9S\x05%x\x01\x00\x00x\x01\x00\x00\x0e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x816,\x00\x00migrations.sqlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc1h\xf1\xfb/\x00\x00\x00/\x00\x00\x00\x06\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xed\x81\xf3-\x00\x00new.shUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x15\x00\x15\x00\xcc\x07\x00\x00_.\x00\x00\x00\x00" diff --git a/compose/db/schema/mysql/20190427210922.namespace-tbl.up.sql b/compose/db/schema/mysql/20190427210922.namespace-tbl.up.sql new file mode 100644 index 000000000..3be6620b5 --- /dev/null +++ b/compose/db/schema/mysql/20190427210922.namespace-tbl.up.sql @@ -0,0 +1,14 @@ +CREATE TABLE `compose_namespace` ( + `id` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(64) NOT NULL COMMENT 'Name', + `slug` VARCHAR(64) NOT NULL COMMENT 'URL slug', + `enabled` BOOLEAN NOT NULL COMMENT 'Is namespace enabled?', + `meta` JSON NOT NULL COMMENT 'Meta data', + + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME DEFAULT NULL, + `deleted_at` DATETIME DEFAULT NULL, + + PRIMARY KEY (`id`) + +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/compose/internal/repository/error.go b/compose/internal/repository/error.go new file mode 100644 index 000000000..f1d4dd37f --- /dev/null +++ b/compose/internal/repository/error.go @@ -0,0 +1,25 @@ +package repository + +import ( + "github.com/pkg/errors" +) + +type ( + repositoryError string +) + +const ( + ErrNotImplemented = repositoryError("NotImplemented") +) + +func (e repositoryError) Error() string { + return e.String() +} + +func (e repositoryError) String() string { + return "crust.compose.repository." + string(e) +} + +func (e repositoryError) new() error { + return errors.WithStack(e) +} diff --git a/compose/internal/repository/namespace.go b/compose/internal/repository/namespace.go new file mode 100644 index 000000000..b6a2af4ce --- /dev/null +++ b/compose/internal/repository/namespace.go @@ -0,0 +1,168 @@ +package repository + +import ( + "context" + "time" + + "github.com/titpetric/factory" + "gopkg.in/Masterminds/squirrel.v1" + + "github.com/crusttech/crust/compose/types" +) + +type ( + NamespaceRepository interface { + With(ctx context.Context, db *factory.DB) NamespaceRepository + + FindByID(id uint64) (*types.Namespace, error) + Find(filter types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) + Create(mod *types.Namespace) (*types.Namespace, error) + Update(mod *types.Namespace) (*types.Namespace, error) + DeleteByID(id uint64) error + } + + namespace struct { + *repository + } +) + +const ( + ErrNamespaceNotFound = repositoryError("NamespaceNotFound") +) + +func Namespace(ctx context.Context, db *factory.DB) NamespaceRepository { + return (&namespace{}).With(ctx, db) +} + +func (r namespace) table() string { + return "compose_namespace" +} + +func (r namespace) columns() []string { + return []string{ + "id", + "name", + "slug", + "enabled", + "meta", + "created_at", + "updated_at", + "deleted_at", + } +} + +func (r *namespace) With(ctx context.Context, db *factory.DB) NamespaceRepository { + return &namespace{ + repository: r.repository.With(ctx, db), + } +} + +func (r *namespace) FindByID(id uint64) (n *types.Namespace, err error) { + var ( + sql string + args []interface{} + ) + + n = &types.Namespace{} + + qb := r.query(). + Columns(r.columns()...). + Where("id = ?", id) + + if sql, args, err = qb.ToSql(); err != nil { + return + } + + if err = r.db().Get(n, sql, args...); err != nil { + return + } + + if n == nil || n.ID == 0 { + return nil, ErrNamespaceNotFound + } + + return +} + +func (r *namespace) Find(filter types.NamespaceFilter) (nn types.NamespaceSet, f types.NamespaceFilter, err error) { + var ( + sql string + args []interface{} + ) + + f = filter + + if f.PerPage > 100 { + f.PerPage = 100 + } else if f.PerPage == 0 { + f.PerPage = 50 + } + + qb := r.query() + if f.Query != "" { + q := "%" + f.Query + "%" + qb = qb.Where("name like ? OR slug like ?", q, q) + } + + { + cq := qb.Column(squirrel.Alias(squirrel.Expr("COUNT(*)"), "count")) + if sql, args, err = cq.ToSql(); err != nil { + return + } + + if err = r.db().Get(&f.Count, sql, args...); err != nil { + return + } + + if f.Count == 0 { + // No rows with this filter no need to continue + return + } + } + + if f.Page > 0 { + qb = qb.Offset(uint64(f.PerPage * f.Page)) + } + + qb = qb. + Limit(uint64(f.PerPage)). + Columns(r.columns()...). + OrderBy("id ASC") + + if sql, args, err = qb.ToSql(); err != nil { + return + } + + if err = r.db().Select(&nn, sql, args...); err != nil { + return + } + + return +} + +func (r namespace) query() squirrel.SelectBuilder { + return squirrel. + Select(). + From(r.table()). + Where("deleted_at IS NULL") + +} + +func (r *namespace) Create(mod *types.Namespace) (*types.Namespace, error) { + mod.ID = factory.Sonyflake.NextID() + mod.CreatedAt = time.Now() + + return mod, r.db().Insert(r.table(), mod) +} + +func (r *namespace) Update(mod *types.Namespace) (*types.Namespace, error) { + now := time.Now() + mod.UpdatedAt = &now + + return mod, r.db().Replace(r.table(), mod) +} + +func (r *namespace) DeleteByID(id uint64) error { + _, err := r.db().Exec("DELETE FROM compose_namespace WHERE id=?", id) + return err +} diff --git a/compose/internal/service/error.go b/compose/internal/service/error.go new file mode 100644 index 000000000..ae7b9751a --- /dev/null +++ b/compose/internal/service/error.go @@ -0,0 +1,31 @@ +package service + +import ( + "github.com/pkg/errors" +) + +type ( + serviceError string +) + +const ( + ErrInvalidID serviceError = "InvalidID" + ErrStaleData serviceError = "StaleData" + ErrNoCreatePermissions serviceError = "NoCreatePermissions" + ErrNoReadPermissions serviceError = "NoReadPermissions" + ErrNoUpdatePermissions serviceError = "NoUpdatePermissions" + ErrNoDeletePermissions serviceError = "NoDeletePermissions" + ErrNotImplemented serviceError = "NotImplemented" +) + +func (e serviceError) Error() string { + return e.String() +} + +func (e serviceError) String() string { + return "crust.compose.service." + string(e) +} + +func (e serviceError) withStack() error { + return errors.WithStack(e) +} diff --git a/compose/internal/service/namespace.go b/compose/internal/service/namespace.go new file mode 100644 index 000000000..e544c49fd --- /dev/null +++ b/compose/internal/service/namespace.go @@ -0,0 +1,132 @@ +package service + +import ( + "context" + "time" + + "github.com/titpetric/factory" + + "github.com/crusttech/crust/compose/internal/repository" + "github.com/crusttech/crust/compose/types" +) + +type ( + namespace struct { + db *factory.DB + ctx context.Context + + prmSvc PermissionsService + + namespaceRepo repository.NamespaceRepository + } + + NamespaceService interface { + With(ctx context.Context) NamespaceService + + FindByID(namespaceID uint64) (*types.Namespace, error) + Find(types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) + + Create(namespace *types.Namespace) (*types.Namespace, error) + Update(namespace *types.Namespace) (*types.Namespace, error) + DeleteByID(namespaceID uint64) error + } +) + +func Namespace() NamespaceService { + return (&namespace{ + prmSvc: DefaultPermissions, + }).With(context.Background()) +} + +func (svc *namespace) With(ctx context.Context) NamespaceService { + db := repository.DB(ctx) + return &namespace{ + db: db, + ctx: ctx, + + prmSvc: svc.prmSvc.With(ctx), + + namespaceRepo: repository.Namespace(ctx, db), + } +} + +func (svc *namespace) FindByID(ID uint64) (n *types.Namespace, err error) { + if ID == 0 { + return nil, ErrInvalidID.withStack() + } + + if n, err = svc.namespaceRepo.FindByID(ID); err != nil { + return + } else if !svc.prmSvc.CanReadNamespace(n) { + return nil, ErrNoReadPermissions.withStack() + } + + return +} + +func (svc *namespace) Find(filter types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error) { + nn, f, err := svc.namespaceRepo.Find(filter) + if err != nil { + return nil, f, err + } + + nn, _ = nn.Filter(func(m *types.Namespace) (bool, error) { + return svc.prmSvc.CanReadNamespace(m), nil + }) + + return nn, f, nil +} + +func (svc *namespace) Create(mod *types.Namespace) (*types.Namespace, error) { + if !svc.prmSvc.CanCreateNamespace() { + return nil, ErrNoCreatePermissions.withStack() + } + + return svc.namespaceRepo.Create(mod) +} + +func (svc *namespace) Update(updated *types.Namespace) (m *types.Namespace, err error) { + m, err = svc.FindByID(updated.ID) + if err != nil { + return nil, err + } + + if isStale(updated.UpdatedAt, m.UpdatedAt, m.CreatedAt) { + return nil, ErrStaleData + } + + if !svc.prmSvc.CanUpdateNamespace(m) { + return nil, ErrNoUpdatePermissions.withStack() + } + + m.Name = updated.Name + m.Slug = updated.Slug + m.Meta = updated.Meta + m.Enabled = updated.Enabled + + return svc.namespaceRepo.Update(m) +} + +func (svc *namespace) DeleteByID(ID uint64) error { + if m, err := svc.namespaceRepo.FindByID(ID); err != nil { + return err + } else if !svc.prmSvc.CanDeleteNamespace(m) { + return ErrNoDeletePermissions.withStack() + } + + return svc.namespaceRepo.DeleteByID(ID) +} + +// Data is stale when new date does not match updatedAt or createdAt (before first update) +func isStale(new *time.Time, updatedAt *time.Time, createdAt time.Time) bool { + + if new == nil { + return false + } + + if updatedAt != nil { + return !new.Equal(*updatedAt) + } + + return new.Equal(createdAt) +} diff --git a/compose/internal/service/permissions.go b/compose/internal/service/permissions.go index dcec64e95..46a5a10ae 100644 --- a/compose/internal/service/permissions.go +++ b/compose/internal/service/permissions.go @@ -27,7 +27,10 @@ type ( Effective() (ee []effectivePermission, err error) CanAccess() bool - CanCreateNamspace() bool + CanCreateNamespace() bool + CanReadNamespace(r permissionResource) bool + CanUpdateNamespace(r permissionResource) bool + CanDeleteNamespace(r permissionResource) bool CanCreateModule(r permissionResource) bool CanReadModule(r permissionResource) bool CanUpdateModule(r permissionResource) bool @@ -95,7 +98,7 @@ func (p *permissions) Effective() (ee []effectivePermission, err error) { ee = append(ee, ep("compose", "access", p.CanAccess())) ee = append(ee, ep("compose", "grant", p.CanGrant())) - ee = append(ee, ep("compose", "namespace.create", p.CanCreateNamspace())) + ee = append(ee, ep("compose", "namespace.create", p.CanCreateNamespace())) ee = append(ee, ep("compose:namespace:crm", "module.create", p.CanCreateModule(crmNamespace()))) ee = append(ee, ep("compose:namespace:crm", "chart.create", p.CanCreateChart(crmNamespace()))) @@ -113,13 +116,24 @@ func (p *permissions) CanGrant() bool { return p.checkAccess(types.PermissionResource, "grant") } -func (p *permissions) CanCreateNamspace() bool { +func (p *permissions) CanCreateNamespace() bool { return p.checkAccess(types.PermissionResource, "namespace.create") } -func (p *permissions) CanCreateModule(ns permissionResource) bool { - // @todo move to func args when namespaces are implemented - return p.checkAccess(ns, "module.create") +func (p *permissions) CanReadNamespace(r permissionResource) bool { + return p.checkAccess(r, "read", p.allow()) +} + +func (p *permissions) CanUpdateNamespace(r permissionResource) bool { + return p.checkAccess(r, "update") +} + +func (p *permissions) CanDeleteNamespace(r permissionResource) bool { + return p.checkAccess(r, "delete") +} + +func (p *permissions) CanCreateModule(r permissionResource) bool { + return p.checkAccess(r, "module.create") } func (p *permissions) CanReadModule(r permissionResource) bool { @@ -206,3 +220,9 @@ func (p *permissions) checkAccess(resource permissionResource, operation string, } return false } + +func (p permissions) allow() func() internalRules.Access { + return func() internalRules.Access { + return internalRules.Allow + } +} diff --git a/compose/internal/service/service.go b/compose/internal/service/service.go index 5cbb01218..7a1416380 100644 --- a/compose/internal/service/service.go +++ b/compose/internal/service/service.go @@ -19,6 +19,7 @@ var ( DefaultNotification NotificationService DefaultPermissions PermissionsService DefaultAttachment AttachmentService + DefaultNamespace NamespaceService ) func Init() error { @@ -35,6 +36,7 @@ func Init() error { DefaultChart = Chart() DefaultNotification = Notification() DefaultAttachment = Attachment(fs) + DefaultNamespace = Namespace() return nil } diff --git a/compose/rest/attachment.go b/compose/rest/attachment.go index 7f9babf0a..c150b2f07 100644 --- a/compose/rest/attachment.go +++ b/compose/rest/attachment.go @@ -30,12 +30,6 @@ type ( UpdatedAt *time.Time `json:"updatedAt,omitempty"` } - file struct { - *types.Attachment - content io.ReadSeeker - download bool - } - Attachment struct { attachment service.AttachmentService } @@ -176,23 +170,3 @@ func makeAttachmentPayload(a *types.Attachment, userID uint64) *attachmentPayloa UpdatedAt: a.UpdatedAt, } } - -func (f *file) Download() bool { - return f.download -} - -func (f *file) Name() string { - return f.Attachment.Name -} - -func (f *file) ModTime() time.Time { - return f.Attachment.CreatedAt -} - -func (f *file) Content() io.ReadSeeker { - return f.content -} - -func (f *file) Valid() bool { - return f.content != nil -} diff --git a/compose/rest/handlers/namespace.go b/compose/rest/handlers/namespace.go new file mode 100644 index 000000000..4594d6a7b --- /dev/null +++ b/compose/rest/handlers/namespace.go @@ -0,0 +1,161 @@ +package handlers + +/* + Hello! This file is auto-generated from `docs/src/spec.json`. + + For development: + In order to update the generated files, edit this file under the location, + add your struct fields, imports, API definitions and whatever you want, and: + + 1. run [spec](https://github.com/titpetric/spec) in the same folder, + 2. run `./_gen.php` in this folder. + + You may edit `namespace.go`, `namespace.util.go` or `namespace_test.go` to + implement your API calls, helper functions and tests. The file `namespace.go` + is only generated the first time, and will not be overwritten if it exists. +*/ + +import ( + "context" + + "net/http" + + "github.com/go-chi/chi" + "github.com/titpetric/factory/resputil" + + "github.com/crusttech/crust/compose/rest/request" +) + +// Internal API interface +type NamespaceAPI interface { + List(context.Context, *request.NamespaceList) (interface{}, error) + Create(context.Context, *request.NamespaceCreate) (interface{}, error) + Read(context.Context, *request.NamespaceRead) (interface{}, error) + Update(context.Context, *request.NamespaceUpdate) (interface{}, error) + Delete(context.Context, *request.NamespaceDelete) (interface{}, error) +} + +// HTTP API interface +type Namespace struct { + List func(http.ResponseWriter, *http.Request) + Create func(http.ResponseWriter, *http.Request) + Read func(http.ResponseWriter, *http.Request) + Update func(http.ResponseWriter, *http.Request) + Delete func(http.ResponseWriter, *http.Request) +} + +func NewNamespace(nh NamespaceAPI) *Namespace { + return &Namespace{ + List: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceList() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.List(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + Create: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceCreate() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.Create(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + Read: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceRead() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.Read(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + Update: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceUpdate() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.Update(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + Delete: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceDelete() + if err := params.Fill(r); err != nil { + resputil.JSON(w, err) + return + } + if value, err := nh.Delete(r.Context(), params); err != nil { + resputil.JSON(w, err) + return + } else { + switch fn := value.(type) { + case func(http.ResponseWriter, *http.Request): + fn(w, r) + return + } + resputil.JSON(w, value) + return + } + }, + } +} + +func (nh *Namespace) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { + r.Group(func(r chi.Router) { + r.Use(middlewares...) + r.Get("/namespace/", nh.List) + r.Post("/namespace/", nh.Create) + r.Get("/namespace/{namespaceID}", nh.Read) + r.Post("/namespace/{namespaceID}", nh.Update) + r.Delete("/namespace/{namespaceID}", nh.Delete) + }) +} diff --git a/compose/rest/namespace.go b/compose/rest/namespace.go new file mode 100644 index 000000000..2c693756d --- /dev/null +++ b/compose/rest/namespace.go @@ -0,0 +1,134 @@ +package rest + +import ( + "context" + + "github.com/titpetric/factory/resputil" + + "github.com/crusttech/crust/compose/internal/service" + "github.com/crusttech/crust/compose/rest/request" + "github.com/crusttech/crust/compose/types" +) + +type ( + namespacePayload struct { + *types.Namespace + + CanUpdateNamespace bool `json:"canUpdateNamespace"` + CanDeleteNamespace bool `json:"canDeleteNamespace"` + CanCreateModule bool `json:"canCreateModule"` + CanCreateChart bool `json:"canCreateChart"` + CanCreateTrigger bool `json:"canCreateTrigger"` + CanCreatePage bool `json:"canCreatePage"` + } + + namespaceSetPayload struct { + Filter types.NamespaceFilter `json:"filter"` + Set []*namespacePayload `json:"set"` + } +) + +type Namespace struct { + namespace service.NamespaceService + permissions service.PermissionsService +} + +func (Namespace) New() *Namespace { + return &Namespace{ + namespace: service.DefaultNamespace, + permissions: service.DefaultPermissions, + } +} + +func (ctrl Namespace) List(ctx context.Context, r *request.NamespaceList) (interface{}, error) { + f := types.NamespaceFilter{ + Query: r.Query, + PerPage: r.PerPage, + Page: r.Page, + } + + nn, filter, err := ctrl.namespace.With(ctx).Find(f) + return ctrl.makeFilterPayload(ctx, nn, filter, err) +} + +func (ctrl Namespace) Create(ctx context.Context, r *request.NamespaceCreate) (interface{}, error) { + var err error + ns := &types.Namespace{ + Name: r.Name, + Slug: r.Slug, + Meta: r.Meta, + Enabled: r.Enabled, + } + + ns, err = ctrl.namespace.With(ctx).Create(ns) + return ctrl.makePayload(ctx, ns, err) +} + +func (ctrl Namespace) Read(ctx context.Context, r *request.NamespaceRead) (interface{}, error) { + ns, err := ctrl.namespace.With(ctx).FindByID(r.NamespaceID) + return ctrl.makePayload(ctx, ns, err) +} + +func (ctrl Namespace) Update(ctx context.Context, r *request.NamespaceUpdate) (interface{}, error) { + var ( + ns = &types.Namespace{} + err error + ) + + ns.ID = r.NamespaceID + ns.Name = r.Name + ns.Slug = r.Slug + ns.Meta = r.Meta + ns.Enabled = r.Enabled + ns.UpdatedAt = r.UpdatedAt + + ns, err = ctrl.namespace.With(ctx).Update(ns) + return ctrl.makePayload(ctx, ns, err) +} + +func (ctrl Namespace) Delete(ctx context.Context, r *request.NamespaceDelete) (interface{}, error) { + _, err := ctrl.namespace.With(ctx).FindByID(r.NamespaceID) + if err != nil { + return nil, err + } + + err = ctrl.namespace.With(ctx).DeleteByID(r.NamespaceID) + if err != nil { + return nil, err + } else { + return resputil.Success("deleted"), nil + } +} + +func (ctrl Namespace) makePayload(ctx context.Context, ns *types.Namespace, err error) (*namespacePayload, error) { + if err != nil { + return nil, err + } + + perm := ctrl.permissions.With(ctx) + + return &namespacePayload{ + Namespace: ns, + + CanUpdateNamespace: perm.CanUpdateNamespace(ns), + CanDeleteNamespace: perm.CanDeleteNamespace(ns), + CanCreateModule: perm.CanCreateModule(ns), + CanCreateChart: perm.CanCreateChart(ns), + CanCreateTrigger: perm.CanCreateTrigger(ns), + CanCreatePage: perm.CanCreatePage(ns), + }, nil +} + +func (ctrl Namespace) makeFilterPayload(ctx context.Context, nn types.NamespaceSet, f types.NamespaceFilter, err error) (*namespaceSetPayload, error) { + if err != nil { + return nil, err + } + + nsp := &namespaceSetPayload{Filter: f, Set: make([]*namespacePayload, len(nn))} + + for i := range nn { + nsp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil) + } + + return nsp, nil +} diff --git a/compose/rest/request/namespace.go b/compose/rest/request/namespace.go new file mode 100644 index 000000000..125f6d337 --- /dev/null +++ b/compose/rest/request/namespace.go @@ -0,0 +1,311 @@ +package request + +/* + Hello! This file is auto-generated from `docs/src/spec.json`. + + For development: + In order to update the generated files, edit this file under the location, + add your struct fields, imports, API definitions and whatever you want, and: + + 1. run [spec](https://github.com/titpetric/spec) in the same folder, + 2. run `./_gen.php` in this folder. + + You may edit `namespace.go`, `namespace.util.go` or `namespace_test.go` to + implement your API calls, helper functions and tests. The file `namespace.go` + is only generated the first time, and will not be overwritten if it exists. +*/ + +import ( + "io" + "strings" + + "encoding/json" + "mime/multipart" + "net/http" + + "github.com/go-chi/chi" + "github.com/pkg/errors" + + sqlxTypes "github.com/jmoiron/sqlx/types" + "time" +) + +var _ = chi.URLParam +var _ = multipart.FileHeader{} + +// Namespace list request parameters +type NamespaceList struct { + Query string + Page uint + PerPage uint +} + +func NewNamespaceList() *NamespaceList { + return &NamespaceList{} +} + +func (nReq *NamespaceList) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + if val, ok := get["query"]; ok { + + nReq.Query = val + } + if val, ok := get["page"]; ok { + + nReq.Page = parseUint(val) + } + if val, ok := get["perPage"]; ok { + + nReq.PerPage = parseUint(val) + } + + return err +} + +var _ RequestFiller = NewNamespaceList() + +// Namespace create request parameters +type NamespaceCreate struct { + Name string + Slug string + Enabled bool + Meta sqlxTypes.JSONText +} + +func NewNamespaceCreate() *NamespaceCreate { + return &NamespaceCreate{} +} + +func (nReq *NamespaceCreate) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + if val, ok := post["name"]; ok { + + nReq.Name = val + } + if val, ok := post["slug"]; ok { + + nReq.Slug = val + } + if val, ok := post["enabled"]; ok { + + nReq.Enabled = parseBool(val) + } + if val, ok := post["meta"]; ok { + + if nReq.Meta, err = parseJSONTextWithErr(val); err != nil { + return err + } + } + + return err +} + +var _ RequestFiller = NewNamespaceCreate() + +// Namespace read request parameters +type NamespaceRead struct { + NamespaceID uint64 `json:",string"` +} + +func NewNamespaceRead() *NamespaceRead { + return &NamespaceRead{} +} + +func (nReq *NamespaceRead) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + nReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) + + return err +} + +var _ RequestFiller = NewNamespaceRead() + +// Namespace update request parameters +type NamespaceUpdate struct { + NamespaceID uint64 `json:",string"` + Name string + Slug string + Enabled bool + Meta sqlxTypes.JSONText + UpdatedAt *time.Time +} + +func NewNamespaceUpdate() *NamespaceUpdate { + return &NamespaceUpdate{} +} + +func (nReq *NamespaceUpdate) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + nReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) + if val, ok := post["name"]; ok { + + nReq.Name = val + } + if val, ok := post["slug"]; ok { + + nReq.Slug = val + } + if val, ok := post["enabled"]; ok { + + nReq.Enabled = parseBool(val) + } + if val, ok := post["meta"]; ok { + + if nReq.Meta, err = parseJSONTextWithErr(val); err != nil { + return err + } + } + if val, ok := post["updatedAt"]; ok { + + if nReq.UpdatedAt, err = parseISODatePtrWithErr(val); err != nil { + return err + } + } + + return err +} + +var _ RequestFiller = NewNamespaceUpdate() + +// Namespace delete request parameters +type NamespaceDelete struct { + NamespaceID uint64 `json:",string"` +} + +func NewNamespaceDelete() *NamespaceDelete { + return &NamespaceDelete{} +} + +func (nReq *NamespaceDelete) Fill(r *http.Request) (err error) { + if strings.ToLower(r.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(r.Body).Decode(nReq) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return errors.Wrap(err, "error parsing http request body") + } + } + + if err = r.ParseForm(); err != nil { + return err + } + + get := map[string]string{} + post := map[string]string{} + urlQuery := r.URL.Query() + for name, param := range urlQuery { + get[name] = string(param[0]) + } + postVars := r.Form + for name, param := range postVars { + post[name] = string(param[0]) + } + + nReq.NamespaceID = parseUInt64(chi.URLParam(r, "namespaceID")) + + return err +} + +var _ RequestFiller = NewNamespaceDelete() diff --git a/compose/rest/request/util.go b/compose/rest/request/util.go index 4a1d7096e..fc9a011d5 100644 --- a/compose/rest/request/util.go +++ b/compose/rest/request/util.go @@ -4,6 +4,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/jmoiron/sqlx/types" "github.com/pkg/errors" @@ -17,6 +18,19 @@ func parseJSONTextWithErr(s string) (types.JSONText, error) { return *result, err } +func parseISODateWithErr(s string) (time.Time, error) { + return time.Parse(time.RFC3339, s) +} + +func parseISODatePtrWithErr(s string) (*time.Time, error) { + t, err := parseISODateWithErr(s) + if err != nil { + return nil, err + } + + return &t, nil +} + // parseInt parses a string to int func parseInt(s string) int { if s == "" { diff --git a/compose/rest/router.go b/compose/rest/router.go index a0918fe54..ebbbcad76 100644 --- a/compose/rest/router.go +++ b/compose/rest/router.go @@ -9,6 +9,7 @@ import ( func MountRoutes() func(chi.Router) { var ( + namespace = Namespace{}.New() module = Module{}.New() record = Record{}.New() page = Page{}.New() @@ -16,8 +17,6 @@ func MountRoutes() func(chi.Router) { trigger = Trigger{}.New() notification = Notification{}.New() attachment = Attachment{}.New() - // pageAttachment = PageAttachment{}.New() - // recordAttachment = RecordAttachment{}.New() ) // Initialize handlers & controllers. @@ -32,6 +31,7 @@ func MountRoutes() func(chi.Router) { r.Use(auth.MiddlewareValidOnly) r.Use(middlewareAllowedAccess) + handlers.NewNamespace(namespace).MountRoutes(r) handlers.NewPage(page).MountRoutes(r) handlers.NewModule(module).MountRoutes(r) handlers.NewRecord(record).MountRoutes(r) diff --git a/compose/types/namespace.gen.go b/compose/types/namespace.gen.go new file mode 100644 index 000000000..55f87f097 --- /dev/null +++ b/compose/types/namespace.gen.go @@ -0,0 +1,67 @@ +package types + +// Hello! This file is auto-generated. + +type ( + + // NamespaceSet slice of Namespace + // + // This type is auto-generated. + NamespaceSet []*Namespace +) + +// Walk iterates through every slice item and calls w(Namespace) err +// +// This function is auto-generated. +func (set NamespaceSet) Walk(w func(*Namespace) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(Namespace) (bool, err) and return filtered slice +// +// This function is auto-generated. +func (set NamespaceSet) Filter(f func(*Namespace) (bool, error)) (out NamespaceSet, err error) { + var ok bool + out = NamespaceSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// FindByID finds items from slice by its ID property +// +// This function is auto-generated. +func (set NamespaceSet) FindByID(ID uint64) *Namespace { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// IDs returns a slice of uint64s from all items in the set +// +// This function is auto-generated. +func (set NamespaceSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} diff --git a/compose/types/namespace.go b/compose/types/namespace.go index 3a60ecd3f..ef1390ef7 100644 --- a/compose/types/namespace.go +++ b/compose/types/namespace.go @@ -1,12 +1,32 @@ package types import ( + "time" + + "github.com/jmoiron/sqlx/types" + "github.com/crusttech/crust/internal/rules" ) type ( Namespace struct { - ID uint64 `json:"id,string" db:"id"` + ID uint64 `json:"namespaceID,string" db:"id"` + Name string `json:"name" db:"name"` + Slug string `json:"slug" db:"slug"` + Enabled bool `json:"enabled" db:"enabled"` + Meta types.JSONText `json:"meta" db:"meta"` + + CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"` + UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at"` + DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"` + } + + NamespaceFilter struct { + Query string `json:"query"` + Page uint `json:"page"` + PerPage uint `json:"perPage"` + Sort string `json:"sort"` + Count uint `json:"count"` } ) diff --git a/docs/compose/README.md b/docs/compose/README.md new file mode 100644 index 000000000..c9551c3d1 --- /dev/null +++ b/docs/compose/README.md @@ -0,0 +1,788 @@ +# Attachments + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/attachment/{kind}/` | List, filter all page attachments | +| `GET` | `/attachment/{kind}/{attachmentID}` | Attachment details | +| `GET` | `/attachment/{kind}/{attachmentID}/original/{name}` | Serves attached file | +| `GET` | `/attachment/{kind}/{attachmentID}/preview.{ext}` | Serves preview of an attached file | + +## List, filter all page attachments + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/attachment/{kind}/` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | GET | Filter attachments by page ID | N/A | NO | +| moduleID | uint64 | GET | Filter attachments by mnodule ID | N/A | NO | +| recordID | uint64 | GET | Filter attachments by record ID | N/A | NO | +| fieldName | string | GET | Filter attachments by field name | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | +| sign | string | GET | Signature | N/A | YES | +| userID | uint64 | GET | User ID | N/A | YES | +| kind | string | PATH | Attachment kind | N/A | YES | + +## Attachment details + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/attachment/{kind}/{attachmentID}` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | +| kind | string | PATH | Attachment kind | N/A | YES | +| sign | string | GET | Signature | N/A | YES | +| userID | uint64 | GET | User ID | N/A | YES | + +## Serves attached file + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/attachment/{kind}/{attachmentID}/original/{name}` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| download | bool | GET | Force file download | N/A | NO | +| sign | string | GET | Signature | N/A | YES | +| userID | uint64 | GET | User ID | N/A | YES | +| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | +| name | string | PATH | File name | N/A | YES | +| kind | string | PATH | Attachment kind | N/A | YES | + +## Serves preview of an attached file + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/attachment/{kind}/{attachmentID}/preview.{ext}` | HTTP/S | GET | Client ID, Session ID | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| attachmentID | uint64 | PATH | Attachment ID | N/A | YES | +| ext | string | PATH | Preview extension/format | N/A | YES | +| kind | string | PATH | Attachment kind | N/A | YES | +| sign | string | GET | Signature | N/A | YES | +| userID | uint64 | GET | User ID | N/A | YES | + +--- + + + + +# Charts + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/chart/` | List/read charts from module section | +| `POST` | `/chart/` | List/read charts from module section | +| `GET` | `/chart/{chartID}` | Read charts by ID from module section | +| `POST` | `/chart/{chartID}` | Add/update charts in module section | +| `DELETE` | `/chart/{chartID}` | Delete chart | + +## List/read charts from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | + +## List/read charts from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| config | sqlxTypes.JSONText | POST | Chart JSON | N/A | YES | +| name | string | POST | Chart name | N/A | YES | + +## Read charts by ID from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/{chartID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| chartID | uint64 | PATH | Chart ID | N/A | YES | + +## Add/update charts in module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/{chartID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| chartID | uint64 | PATH | Chart ID | N/A | YES | +| config | sqlxTypes.JSONText | POST | Chart JSON | N/A | YES | +| name | string | POST | Chart name | N/A | YES | + +## Delete chart + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/chart/{chartID}` | HTTP/S | DELETE | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| chartID | uint64 | PATH | Chart ID | N/A | YES | + +--- + + + + +# Modules + +Compose module definitions + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/module/` | List modules | +| `POST` | `/module/` | Create module | +| `GET` | `/module/{moduleID}` | Read module | +| `POST` | `/module/{moduleID}` | Update module | +| `DELETE` | `/module/{moduleID}` | Delete module | + +## List modules + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| query | string | GET | Search query | N/A | NO | + +## Create module + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| name | string | POST | Module Name | N/A | YES | +| fields | types.ModuleFieldSet | POST | Fields JSON | N/A | YES | +| meta | sqlxTypes.JSONText | POST | Module meta data | N/A | YES | + +## Read module + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Update module + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | PATH | Module ID | N/A | YES | +| name | string | POST | Module Name | N/A | YES | +| fields | types.ModuleFieldSet | POST | Fields JSON | N/A | YES | +| meta | sqlxTypes.JSONText | POST | Module meta data | N/A | YES | + +## Delete module + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}` | HTTP/S | DELETE | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +--- + + + + +# Namespaces + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/namespace/` | List namespaces | +| `POST` | `/namespace/` | Create namespace | +| `GET` | `/namespace/{namespaceID}` | Read namespace | +| `POST` | `/namespace/{namespaceID}` | Update namespace | +| `DELETE` | `/namespace/{namespaceID}` | Delete namespace | + +## List namespaces + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| query | string | GET | Search query | N/A | NO | +| page | uint | GET | Page number (0 based) | N/A | NO | +| perPage | uint | GET | Returned items per page (default 50) | N/A | NO | + +## Create namespace + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| name | string | POST | Name | N/A | YES | +| slug | string | POST | Slug (url path part) | N/A | YES | +| enabled | bool | POST | Enabled | N/A | YES | +| meta | sqlxTypes.JSONText | POST | Meta data | N/A | YES | + +## Read namespace + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/{namespaceID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| namespaceID | uint64 | PATH | ID | N/A | YES | + +## Update namespace + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/{namespaceID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| namespaceID | uint64 | PATH | ID | N/A | YES | +| name | string | POST | Name | N/A | YES | +| slug | string | POST | Slug (url path part) | N/A | YES | +| enabled | bool | POST | Enabled | N/A | YES | +| meta | sqlxTypes.JSONText | POST | Meta data | N/A | YES | +| updatedAt | *time.Time | POST | Last update (or creation) date | N/A | YES | + +## Delete namespace + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/namespace/{namespaceID}` | HTTP/S | DELETE | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| namespaceID | uint64 | PATH | ID | N/A | YES | + +--- + + + + +# Notifications + +Compose Notifications + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `POST` | `/notification/email` | Send email from the Compose | + +## Send email from the Compose + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/notification/email` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| to | []string | POST | Email addresses or Crust user IDs | N/A | YES | +| cc | []string | POST | Email addresses or Crust user IDs | N/A | NO | +| replyTo | string | POST | Crust user ID or email address in reply-to field | N/A | NO | +| subject | string | POST | Email subject | N/A | NO | +| content | sqlxTypes.JSONText | POST | Message content | N/A | YES | + +--- + + + + +# Pages + +Compose module pages + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/page/` | List available pages | +| `POST` | `/page/` | Create page | +| `GET` | `/page/{pageID}` | Get page details | +| `GET` | `/page/tree` | Get page all (non-record) pages, hierarchically | +| `POST` | `/page/{pageID}` | Update page | +| `POST` | `/page/{selfID}/reorder` | Reorder pages | +| `Delete` | `/page/{pageID}` | Delete page | +| `POST` | `/page/{pageID}/attachment` | Uploads attachment to page | + +## List available pages + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| selfID | uint64 | GET | Parent page ID | N/A | NO | + +## Create page + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| selfID | uint64 | POST | Parent Page ID | N/A | NO | +| moduleID | uint64 | POST | Module ID | N/A | NO | +| title | string | POST | Title | N/A | YES | +| description | string | POST | Description | N/A | NO | +| visible | bool | POST | Visible in navigation | N/A | NO | +| blocks | sqlxTypes.JSONText | POST | Blocks JSON | N/A | YES | + +## Get page details + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{pageID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | PATH | Page ID | N/A | YES | + +## Get page all (non-record) pages, hierarchically + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/tree` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | + +## Update page + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{pageID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | PATH | Page ID | N/A | YES | +| selfID | uint64 | POST | Parent Page ID | N/A | NO | +| moduleID | uint64 | POST | Module ID (optional) | N/A | NO | +| title | string | POST | Title | N/A | YES | +| description | string | POST | Description | N/A | NO | +| visible | bool | POST | Visible in navigation | N/A | NO | +| blocks | sqlxTypes.JSONText | POST | Blocks JSON | N/A | YES | + +## Reorder pages + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{selfID}/reorder` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| selfID | uint64 | PATH | Parent page ID | N/A | YES | +| pageIDs | []string | POST | Page ID order | N/A | YES | + +## Delete page + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{pageID}` | HTTP/S | Delete | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | PATH | Page ID | N/A | YES | + +## Uploads attachment to page + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/page/{pageID}/attachment` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| pageID | uint64 | PATH | Page ID | N/A | YES | +| upload | *multipart.FileHeader | POST | File to upload | N/A | YES | + +--- + + + + +# Permissions + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/permissions/effective` | Effective rules for current user | + +## Effective rules for current user + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/permissions/effective` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| resource | string | GET | Show only rules for a specific resource | N/A | NO | + +--- + + + + +# Records + +Compose records + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/module/{moduleID}/record/report` | Generates report from module records | +| `GET` | `/module/{moduleID}/record/` | List/read records from module section | +| `POST` | `/module/{moduleID}/record/` | Create record in module section | +| `GET` | `/module/{moduleID}/record/{recordID}` | Read records by ID from module section | +| `POST` | `/module/{moduleID}/record/{recordID}` | Update records in module section | +| `DELETE` | `/module/{moduleID}/record/{recordID}` | Delete record row from module section | +| `POST` | `/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | Uploads attachment and validates it against record field requirements | + +## Generates report from module records + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/report` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| metrics | string | GET | Metrics (eg: 'SUM(money), MAX(calls)') | N/A | NO | +| dimensions | string | GET | Dimensions (eg: 'DATE(foo), status') | N/A | YES | +| filter | string | GET | Filter (eg: 'DATE(foo) > 2010') | N/A | NO | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## List/read records from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| filter | string | GET | Filtering condition | N/A | NO | +| page | int | GET | Page number (0 based) | N/A | NO | +| perPage | int | GET | Returned items per page (default 50) | N/A | NO | +| sort | string | GET | Sort field (default id desc) | N/A | NO | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Create record in module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| values | types.RecordValueSet | POST | Record values | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Read records by ID from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/{recordID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| recordID | uint64 | PATH | Record ID | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Update records in module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/{recordID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| recordID | uint64 | PATH | Record ID | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | +| values | types.RecordValueSet | POST | Record values | N/A | YES | + +## Delete record row from module section + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/{recordID}` | HTTP/S | DELETE | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| recordID | uint64 | PATH | Record ID | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | + +## Uploads attachment and validates it against record field requirements + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/module/{moduleID}/record/{recordID}/{fieldName}/attachment` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| recordID | uint64 | PATH | Record ID | N/A | YES | +| fieldName | string | PATH | Field name | N/A | YES | +| moduleID | uint64 | PATH | Module ID | N/A | YES | +| upload | *multipart.FileHeader | POST | File to upload | N/A | YES | + +--- + + + + +# Triggers + +Compose Triggers + +| Method | Endpoint | Purpose | +| ------ | -------- | ------- | +| `GET` | `/trigger/` | List available triggers | +| `POST` | `/trigger/` | Create trigger | +| `GET` | `/trigger/{triggerID}` | Get trigger details | +| `POST` | `/trigger/{triggerID}` | Update trigger | +| `Delete` | `/trigger/{triggerID}` | Delete trigger | + +## List available triggers + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | GET | Filter triggers by module | N/A | NO | + +## Create trigger + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| moduleID | uint64 | POST | Module ID | N/A | NO | +| name | string | POST | Name | N/A | YES | +| actions | []string | POST | Actions that trigger this trigger | N/A | NO | +| enabled | bool | POST | Enabled | N/A | NO | +| source | string | POST | Trigger source code | N/A | NO | + +## Get trigger details + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/{triggerID}` | HTTP/S | GET | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| triggerID | uint64 | PATH | Trigger ID | N/A | YES | + +## Update trigger + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/{triggerID}` | HTTP/S | POST | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| triggerID | uint64 | PATH | Trigger ID | N/A | YES | +| moduleID | uint64 | POST | Module ID | N/A | NO | +| name | string | POST | Name | N/A | YES | +| actions | []string | POST | Actions that trigger this trigger | N/A | NO | +| enabled | bool | POST | Enabled | N/A | NO | +| source | string | POST | Trigger source code | N/A | NO | + +## Delete trigger + +#### Method + +| URI | Protocol | Method | Authentication | +| --- | -------- | ------ | -------------- | +| `/trigger/{triggerID}` | HTTP/S | Delete | | + +#### Request parameters + +| Parameter | Type | Method | Description | Default | Required? | +| --------- | ---- | ------ | ----------- | ------- | --------- | +| triggerID | uint64 | PATH | Trigger ID | N/A | YES | + +--- \ No newline at end of file diff --git a/docs/messaging/README.md b/docs/messaging/README.md index 966cb8dea..5ccaac3c9 100644 --- a/docs/messaging/README.md +++ b/docs/messaging/README.md @@ -304,26 +304,6 @@ A channel is a representation of a sequence of messages. It has meta data like c # Messages -Messages represent individual messages in the chat system. Messages are typed, indicating the event which triggered the message. - -Currently expected message types are: - -| Name | Description | -| ---- | ----------- | -| CREATE | The first message when the channel is created | -| TOPIC | A member changed the topic of the channel | -| RENAME | A member renamed the channel | -| MESSAGE | A member posted a message to the channel | -| FILE | A member uploaded a file to the channel | - -The following event types may be sent with a message event: - -| Name | Description | -| ---- | ----------- | -| CREATED | A message has been created on a channel | -| EDITED | A message has been edited by the sender | -| REMOVED | A message has been removed by the sender | - | Method | Endpoint | Purpose | | ------ | -------- | ------- | | `POST` | `/channels/{channelID}/messages/` | Post new message to the channel | diff --git a/system/cli/roles.go b/system/cli/roles.go index 3e2db0b24..a1e550525 100644 --- a/system/cli/roles.go +++ b/system/cli/roles.go @@ -70,10 +70,10 @@ func rolesResetCmd(ctx context.Context, db *factory.DB) func(cmd *cobra.Command, {2, "compose", "namespace.create", 2}, {2, "compose", "access", 2}, {2, "compose", "grant", 2}, - {2, "compose:namespace:*", "page.create", 2}, {2, "compose:namespace:*", "read", 2}, {2, "compose:namespace:*", "update", 2}, {2, "compose:namespace:*", "delete", 2}, + {2, "compose:namespace:*", "page.create", 2}, {2, "compose:namespace:*", "module.create", 2}, {2, "compose:namespace:*", "chart.create", 2}, {2, "compose:namespace:*", "trigger.create", 2}, diff --git a/system/internal/repository/error.go b/system/internal/repository/error.go index 309b60aee..2cc825428 100644 --- a/system/internal/repository/error.go +++ b/system/internal/repository/error.go @@ -14,5 +14,5 @@ func (e repositoryError) Error() string { } func (e repositoryError) String() string { - return "crust.auth.repository." + string(e) + return "crust.system.repository." + string(e) }