Basic (rest) structure for page & record/module attachments
This commit is contained in:
@@ -191,6 +191,30 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "upload",
|
||||||
|
"path": "/{pageID}/attachment",
|
||||||
|
"method": "POST",
|
||||||
|
"title": "Uploads attachment to page",
|
||||||
|
"parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"type": "uint64",
|
||||||
|
"name": "pageID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Page ID"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"post": [
|
||||||
|
{
|
||||||
|
"name": "upload",
|
||||||
|
"type": "*multipart.FileHeader",
|
||||||
|
"required": true,
|
||||||
|
"title": "File to upload"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -320,21 +344,39 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Records",
|
||||||
|
"description": "CRM records ",
|
||||||
|
"entrypoint": "record",
|
||||||
|
"path": "/module/{moduleID}",
|
||||||
|
"authentication": [],
|
||||||
|
"struct": [
|
||||||
{
|
{
|
||||||
"name": "record/report",
|
"imports": [
|
||||||
|
"github.com/crusttech/crust/crm/types"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"type": "uint64",
|
||||||
|
"name": "moduleID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Module ID"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"apis": [
|
||||||
|
{
|
||||||
|
"name": "report",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"title": "Generates report from module records",
|
"title": "Generates report from module records",
|
||||||
"path": "/{moduleID}/report",
|
"path": "/report",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"path": [
|
|
||||||
{
|
|
||||||
"type": "uint64",
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"get": [
|
"get": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -358,19 +400,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "record/list",
|
"name": "list",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"title": "List/read records from module section",
|
"title": "List/read records from module section",
|
||||||
"path": "/{moduleID}/record",
|
"path": "/record",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"path": [
|
|
||||||
{
|
|
||||||
"type": "uint64",
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"get": [
|
"get": [
|
||||||
{
|
{
|
||||||
"name": "filter",
|
"name": "filter",
|
||||||
@@ -400,19 +434,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "record/create",
|
"name": "create",
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
"title": "Create record in module section",
|
"title": "Create record in module section",
|
||||||
"path": "/{moduleID}/record",
|
"path": "/record",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"path": [
|
|
||||||
{
|
|
||||||
"type": "uint64",
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"post": [
|
"post": [
|
||||||
{
|
{
|
||||||
"type": "types.RecordValueSet",
|
"type": "types.RecordValueSet",
|
||||||
@@ -424,18 +450,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "record/read",
|
"name": "read",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"title": "Read records by ID from module section",
|
"title": "Read records by ID from module section",
|
||||||
"path": "/{moduleID}/record/{recordID}",
|
"path": "/record/{recordID}",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"path": [
|
"path": [
|
||||||
{
|
|
||||||
"type": "uint64",
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "uint64",
|
"type": "uint64",
|
||||||
"name": "recordID",
|
"name": "recordID",
|
||||||
@@ -446,18 +466,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "record/update",
|
"name": "update",
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
"title": "Update records in module section",
|
"title": "Update records in module section",
|
||||||
"path": "/{moduleID}/record/{recordID}",
|
"path": "/record/{recordID}",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"path": [
|
"path": [
|
||||||
{
|
|
||||||
"type": "uint64",
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "uint64",
|
"type": "uint64",
|
||||||
"name": "recordID",
|
"name": "recordID",
|
||||||
@@ -476,18 +490,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "record/delete",
|
"name": "delete",
|
||||||
"method": "DELETE",
|
"method": "DELETE",
|
||||||
"title": "Delete record row from module section",
|
"title": "Delete record row from module section",
|
||||||
"path": "/{moduleID}/record/{recordID}",
|
"path": "/record/{recordID}",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"path": [
|
"path": [
|
||||||
{
|
|
||||||
"type": "uint64",
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "uint64",
|
"type": "uint64",
|
||||||
"name": "recordID",
|
"name": "recordID",
|
||||||
@@ -496,6 +504,36 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "upload",
|
||||||
|
"path": "/record/{recordID}/{fieldName}/attachment",
|
||||||
|
"method": "POST",
|
||||||
|
"title": "Uploads attachment and validates it against record field requirements",
|
||||||
|
"parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "recordID",
|
||||||
|
"type": "uint64",
|
||||||
|
"required": true,
|
||||||
|
"title": "Record ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fieldName",
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"title": "Field name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"post": [
|
||||||
|
{
|
||||||
|
"name": "upload",
|
||||||
|
"type": "*multipart.FileHeader",
|
||||||
|
"required": true,
|
||||||
|
"title": "File to upload"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -779,5 +817,140 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Attachments",
|
||||||
|
"path": "/attachment/{kind}",
|
||||||
|
"entrypoint": "attachment",
|
||||||
|
"authentication": [
|
||||||
|
"Client ID",
|
||||||
|
"Session ID"
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "kind",
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"title": "Attachment kind"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"apis": [
|
||||||
|
{
|
||||||
|
"name": "list",
|
||||||
|
"path": "/",
|
||||||
|
"method": "GET",
|
||||||
|
"title": "List, filter all page attachments",
|
||||||
|
"parameters": {
|
||||||
|
"get": [
|
||||||
|
{
|
||||||
|
"name": "pageID",
|
||||||
|
"type": "uint64",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filter attachments by page ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "uint64",
|
||||||
|
"name": "moduleID",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filter attachments by mnodule ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "recordID",
|
||||||
|
"type": "uint64",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filter attachments by record ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fieldName",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filter attachments by field name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "details",
|
||||||
|
"path": "/{attachmentID}",
|
||||||
|
"method": "GET",
|
||||||
|
"title": "Attachment details",
|
||||||
|
"parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "attachmentID",
|
||||||
|
"type": "uint64",
|
||||||
|
"required": true,
|
||||||
|
"title": "Attachment ID"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "original",
|
||||||
|
"path": "/{attachmentID}/original/{name}",
|
||||||
|
"method": "GET",
|
||||||
|
"title": "Serves attached file",
|
||||||
|
"parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "attachmentID",
|
||||||
|
"type": "uint64",
|
||||||
|
"required": true,
|
||||||
|
"title": "Attachment ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"title": "File name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"get": [
|
||||||
|
{
|
||||||
|
"type": "bool",
|
||||||
|
"name": "download",
|
||||||
|
"required": false,
|
||||||
|
"title": "Force file download"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "preview",
|
||||||
|
"path": "/{attachmentID}/preview.{ext}",
|
||||||
|
"method": "GET",
|
||||||
|
"title": "Serves preview of an attached file",
|
||||||
|
"parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "attachmentID",
|
||||||
|
"type": "uint64",
|
||||||
|
"required": true,
|
||||||
|
"title": "Attachment ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ext",
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"title": "Preview extension/format"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
137
api/crm/spec/attachment.json
Normal file
137
api/crm/spec/attachment.json
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
"Title": "Attachments",
|
||||||
|
"Interface": "Attachment",
|
||||||
|
"Struct": null,
|
||||||
|
"Parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "kind",
|
||||||
|
"required": true,
|
||||||
|
"title": "Attachment kind",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Protocol": "",
|
||||||
|
"Authentication": [
|
||||||
|
"Client ID",
|
||||||
|
"Session ID"
|
||||||
|
],
|
||||||
|
"Path": "/attachment/{kind}",
|
||||||
|
"APIs": [
|
||||||
|
{
|
||||||
|
"Name": "list",
|
||||||
|
"Method": "GET",
|
||||||
|
"Title": "List, filter all page attachments",
|
||||||
|
"Path": "/",
|
||||||
|
"Parameters": {
|
||||||
|
"get": [
|
||||||
|
{
|
||||||
|
"name": "pageID",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filter attachments by page ID",
|
||||||
|
"type": "uint64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "moduleID",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filter attachments by mnodule ID",
|
||||||
|
"type": "uint64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "recordID",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filter attachments by record ID",
|
||||||
|
"type": "uint64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fieldName",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filter attachments by field name",
|
||||||
|
"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": "details",
|
||||||
|
"Method": "GET",
|
||||||
|
"Title": "Attachment details",
|
||||||
|
"Path": "/{attachmentID}",
|
||||||
|
"Parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "attachmentID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Attachment ID",
|
||||||
|
"type": "uint64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "original",
|
||||||
|
"Method": "GET",
|
||||||
|
"Title": "Serves attached file",
|
||||||
|
"Path": "/{attachmentID}/original/{name}",
|
||||||
|
"Parameters": {
|
||||||
|
"get": [
|
||||||
|
{
|
||||||
|
"name": "download",
|
||||||
|
"required": false,
|
||||||
|
"title": "Force file download",
|
||||||
|
"type": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "attachmentID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Attachment ID",
|
||||||
|
"type": "uint64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"title": "File name",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "preview",
|
||||||
|
"Method": "GET",
|
||||||
|
"Title": "Serves preview of an attached file",
|
||||||
|
"Path": "/{attachmentID}/preview.{ext}",
|
||||||
|
"Parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "attachmentID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Attachment ID",
|
||||||
|
"type": "uint64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ext",
|
||||||
|
"required": true,
|
||||||
|
"title": "Preview extension/format",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -126,182 +126,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "record/report",
|
|
||||||
"Method": "GET",
|
|
||||||
"Title": "Generates report from module records",
|
|
||||||
"Path": "/{moduleID}/report",
|
|
||||||
"Parameters": {
|
|
||||||
"get": [
|
|
||||||
{
|
|
||||||
"name": "metrics",
|
|
||||||
"required": false,
|
|
||||||
"title": "Metrics (eg: 'SUM(money), MAX(calls)')",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dimensions",
|
|
||||||
"required": true,
|
|
||||||
"title": "Dimensions (eg: 'DATE(foo), status')",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "filter",
|
|
||||||
"required": false,
|
|
||||||
"title": "Filter (eg: 'DATE(foo) \u003e 2010')",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
{
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID",
|
|
||||||
"type": "uint64"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "record/list",
|
|
||||||
"Method": "GET",
|
|
||||||
"Title": "List/read records from module section",
|
|
||||||
"Path": "/{moduleID}/record",
|
|
||||||
"Parameters": {
|
|
||||||
"get": [
|
|
||||||
{
|
|
||||||
"name": "filter",
|
|
||||||
"required": false,
|
|
||||||
"title": "Filtering condition",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "page",
|
|
||||||
"required": false,
|
|
||||||
"title": "Page number (0 based)",
|
|
||||||
"type": "int"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "perPage",
|
|
||||||
"required": false,
|
|
||||||
"title": "Returned items per page (default 50)",
|
|
||||||
"type": "int"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "sort",
|
|
||||||
"required": false,
|
|
||||||
"title": "Sort field (default id desc)",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"path": [
|
|
||||||
{
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID",
|
|
||||||
"type": "uint64"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "record/create",
|
|
||||||
"Method": "POST",
|
|
||||||
"Title": "Create record in module section",
|
|
||||||
"Path": "/{moduleID}/record",
|
|
||||||
"Parameters": {
|
|
||||||
"path": [
|
|
||||||
{
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID",
|
|
||||||
"type": "uint64"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"post": [
|
|
||||||
{
|
|
||||||
"name": "values",
|
|
||||||
"required": true,
|
|
||||||
"title": "Record values",
|
|
||||||
"type": "types.RecordValueSet"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "record/read",
|
|
||||||
"Method": "GET",
|
|
||||||
"Title": "Read records by ID from module section",
|
|
||||||
"Path": "/{moduleID}/record/{recordID}",
|
|
||||||
"Parameters": {
|
|
||||||
"path": [
|
|
||||||
{
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID",
|
|
||||||
"type": "uint64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "recordID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Record ID",
|
|
||||||
"type": "uint64"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "record/update",
|
|
||||||
"Method": "POST",
|
|
||||||
"Title": "Update records in module section",
|
|
||||||
"Path": "/{moduleID}/record/{recordID}",
|
|
||||||
"Parameters": {
|
|
||||||
"path": [
|
|
||||||
{
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID",
|
|
||||||
"type": "uint64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "recordID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Record ID",
|
|
||||||
"type": "uint64"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"post": [
|
|
||||||
{
|
|
||||||
"name": "values",
|
|
||||||
"required": true,
|
|
||||||
"title": "Record values",
|
|
||||||
"type": "types.RecordValueSet"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "record/delete",
|
|
||||||
"Method": "DELETE",
|
|
||||||
"Title": "Delete record row from module section",
|
|
||||||
"Path": "/{moduleID}/record/{recordID}",
|
|
||||||
"Parameters": {
|
|
||||||
"path": [
|
|
||||||
{
|
|
||||||
"name": "moduleID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Module ID",
|
|
||||||
"type": "uint64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "recordID",
|
|
||||||
"required": true,
|
|
||||||
"title": "Record ID",
|
|
||||||
"type": "uint64"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -192,6 +192,30 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "upload",
|
||||||
|
"Method": "POST",
|
||||||
|
"Title": "Uploads attachment to page",
|
||||||
|
"Path": "/{pageID}/attachment",
|
||||||
|
"Parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "pageID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Page ID",
|
||||||
|
"type": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"post": [
|
||||||
|
{
|
||||||
|
"name": "upload",
|
||||||
|
"required": true,
|
||||||
|
"title": "File to upload",
|
||||||
|
"type": "*multipart.FileHeader"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
191
api/crm/spec/record.json
Normal file
191
api/crm/spec/record.json
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
{
|
||||||
|
"Title": "Records",
|
||||||
|
"Description": "CRM records ",
|
||||||
|
"Interface": "Record",
|
||||||
|
"Struct": [
|
||||||
|
{
|
||||||
|
"imports": [
|
||||||
|
"github.com/crusttech/crust/crm/types"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "moduleID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Module ID",
|
||||||
|
"type": "uint64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Protocol": "",
|
||||||
|
"Authentication": [],
|
||||||
|
"Path": "/module/{moduleID}",
|
||||||
|
"APIs": [
|
||||||
|
{
|
||||||
|
"Name": "report",
|
||||||
|
"Method": "GET",
|
||||||
|
"Title": "Generates report from module records",
|
||||||
|
"Path": "/report",
|
||||||
|
"Parameters": {
|
||||||
|
"get": [
|
||||||
|
{
|
||||||
|
"name": "metrics",
|
||||||
|
"required": false,
|
||||||
|
"title": "Metrics (eg: 'SUM(money), MAX(calls)')",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dimensions",
|
||||||
|
"required": true,
|
||||||
|
"title": "Dimensions (eg: 'DATE(foo), status')",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "filter",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filter (eg: 'DATE(foo) \u003e 2010')",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "list",
|
||||||
|
"Method": "GET",
|
||||||
|
"Title": "List/read records from module section",
|
||||||
|
"Path": "/record",
|
||||||
|
"Parameters": {
|
||||||
|
"get": [
|
||||||
|
{
|
||||||
|
"name": "filter",
|
||||||
|
"required": false,
|
||||||
|
"title": "Filtering condition",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"title": "Page number (0 based)",
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "perPage",
|
||||||
|
"required": false,
|
||||||
|
"title": "Returned items per page (default 50)",
|
||||||
|
"type": "int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"required": false,
|
||||||
|
"title": "Sort field (default id desc)",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "create",
|
||||||
|
"Method": "POST",
|
||||||
|
"Title": "Create record in module section",
|
||||||
|
"Path": "/record",
|
||||||
|
"Parameters": {
|
||||||
|
"post": [
|
||||||
|
{
|
||||||
|
"name": "values",
|
||||||
|
"required": true,
|
||||||
|
"title": "Record values",
|
||||||
|
"type": "types.RecordValueSet"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "read",
|
||||||
|
"Method": "GET",
|
||||||
|
"Title": "Read records by ID from module section",
|
||||||
|
"Path": "/record/{recordID}",
|
||||||
|
"Parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "recordID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Record ID",
|
||||||
|
"type": "uint64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "update",
|
||||||
|
"Method": "POST",
|
||||||
|
"Title": "Update records in module section",
|
||||||
|
"Path": "/record/{recordID}",
|
||||||
|
"Parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "recordID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Record ID",
|
||||||
|
"type": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"post": [
|
||||||
|
{
|
||||||
|
"name": "values",
|
||||||
|
"required": true,
|
||||||
|
"title": "Record values",
|
||||||
|
"type": "types.RecordValueSet"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "delete",
|
||||||
|
"Method": "DELETE",
|
||||||
|
"Title": "Delete record row from module section",
|
||||||
|
"Path": "/record/{recordID}",
|
||||||
|
"Parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "recordID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Record ID",
|
||||||
|
"type": "uint64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "upload",
|
||||||
|
"Method": "POST",
|
||||||
|
"Title": "Uploads attachment and validates it against record field requirements",
|
||||||
|
"Path": "/record/{recordID}/{fieldName}/attachment",
|
||||||
|
"Parameters": {
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"name": "recordID",
|
||||||
|
"required": true,
|
||||||
|
"title": "Record ID",
|
||||||
|
"type": "uint64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fieldName",
|
||||||
|
"required": true,
|
||||||
|
"title": "Field name",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"post": [
|
||||||
|
{
|
||||||
|
"name": "upload",
|
||||||
|
"required": true,
|
||||||
|
"title": "File to upload",
|
||||||
|
"type": "*multipart.FileHeader"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -23,9 +23,11 @@ function types {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
./build/gen-type-set --types Module,Page,Chart,Trigger,Record \
|
./build/gen-type-set --types Module,Page,Chart,Trigger,Record \
|
||||||
--output crm/types/type.primary.gen.go
|
--output crm/types/type.primary.gen.go
|
||||||
./build/gen-type-set --with-primary-key=false --types ModuleField,RecordValue \
|
./build/gen-type-set --with-primary-key=false --types ModuleField,RecordValue \
|
||||||
--output crm/types/type.other.gen.go
|
--output crm/types/type.other.gen.go
|
||||||
|
./build/gen-type-set --types Attachment \
|
||||||
|
--output crm/types/attachment.gen.go
|
||||||
|
|
||||||
./build/gen-type-set --types MessageAttachment --output messaging/types/attachment.gen.go
|
./build/gen-type-set --types MessageAttachment --output messaging/types/attachment.gen.go
|
||||||
./build/gen-type-set --with-resources=true --types Channel --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --output messaging/types/channel.gen.go
|
./build/gen-type-set --with-resources=true --types Channel --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --output messaging/types/channel.gen.go
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ $parsers = array(
|
|||||||
"uint64" => "parseUInt64",
|
"uint64" => "parseUInt64",
|
||||||
"[]uint64" => "parseUInt64A",
|
"[]uint64" => "parseUInt64A",
|
||||||
"int" => "parseInt",
|
"int" => "parseInt",
|
||||||
|
"uint" => "parseUint",
|
||||||
"bool" => "parseBool",
|
"bool" => "parseBool",
|
||||||
"sqlxTypes.JSONText" => "parseJSONTextWithErr",
|
"sqlxTypes.JSONText" => "parseJSONTextWithErr",
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
24
crm/db/schema/mysql/20190227090642.attachment.up.sql
Normal file
24
crm/db/schema/mysql/20190227090642.attachment.up.sql
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
CREATE TABLE crm_attachment (
|
||||||
|
id BIGINT UNSIGNED NOT NULL,
|
||||||
|
rel_owner BIGINT UNSIGNED NOT NULL,
|
||||||
|
|
||||||
|
kind VARCHAR(32) NOT NULL,
|
||||||
|
|
||||||
|
url VARCHAR(512),
|
||||||
|
preview_url VARCHAR(512),
|
||||||
|
|
||||||
|
size INT UNSIGNED,
|
||||||
|
mimetype VARCHAR(255),
|
||||||
|
name TEXT,
|
||||||
|
|
||||||
|
meta JSON,
|
||||||
|
|
||||||
|
created_at DATETIME NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at DATETIME NULL,
|
||||||
|
deleted_at DATETIME NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
|
-- page attachments will be referenced via page-block meta data
|
||||||
|
-- module/record attachment will be referenced via crm_record_value
|
||||||
161
crm/repository/attachment.go
Normal file
161
crm/repository/attachment.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/titpetric/factory"
|
||||||
|
sq "gopkg.in/Masterminds/squirrel.v1"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
AttachmentRepository interface {
|
||||||
|
With(ctx context.Context, db *factory.DB) AttachmentRepository
|
||||||
|
|
||||||
|
Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error)
|
||||||
|
FindByID(id uint64) (*types.Attachment, error)
|
||||||
|
FindByIDs(IDs ...uint64) (types.AttachmentSet, error)
|
||||||
|
Create(mod *types.Attachment) (*types.Attachment, error)
|
||||||
|
DeleteByID(id uint64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
attachment struct {
|
||||||
|
*repository
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sqlAttachmentColumns = `
|
||||||
|
a.id, a.rel_owner, a.kind,
|
||||||
|
a.url, a.preview_url,
|
||||||
|
a.name,
|
||||||
|
a.meta,
|
||||||
|
a.created_at, a.updated_at, a.deleted_at
|
||||||
|
`
|
||||||
|
sqlAttachmentScope = "deleted_at IS NULL"
|
||||||
|
|
||||||
|
sqlAttachmentByID = `SELECT ` + sqlAttachmentColumns +
|
||||||
|
` FROM crm_attachment AS a WHERE id = ? AND ` + sqlAttachmentScope
|
||||||
|
|
||||||
|
sqlAttachmentsByIDs = `SELECT ` + sqlAttachmentColumns +
|
||||||
|
` FROM crm_attachment AS a WHERE id IN (?) AND ` + sqlAttachmentScope
|
||||||
|
)
|
||||||
|
|
||||||
|
func Attachment(ctx context.Context, db *factory.DB) AttachmentRepository {
|
||||||
|
return (&attachment{}).With(ctx, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *attachment) With(ctx context.Context, db *factory.DB) AttachmentRepository {
|
||||||
|
return &attachment{
|
||||||
|
repository: r.repository.With(ctx, db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *attachment) FindByID(id uint64) (*types.Attachment, error) {
|
||||||
|
mod := &types.Attachment{}
|
||||||
|
|
||||||
|
return mod, r.db().Get(mod, sqlAttachmentByID, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *attachment) FindByIDs(IDs ...uint64) (rval types.AttachmentSet, err error) {
|
||||||
|
rval = make([]*types.Attachment, 0)
|
||||||
|
|
||||||
|
if len(IDs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sql, args, err := sqlx.In(sqlAttachmentsByIDs, IDs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return rval, r.db().Select(&rval, sql, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *attachment) Find(filter types.AttachmentFilter) (set types.AttachmentSet, f types.AttachmentFilter, err error) {
|
||||||
|
f = filter
|
||||||
|
if f.PerPage > 100 {
|
||||||
|
f.PerPage = 100
|
||||||
|
} else if f.PerPage == 0 {
|
||||||
|
f.PerPage = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
set = types.AttachmentSet{}
|
||||||
|
|
||||||
|
query := sq.Select().From("crm_attachment AS a").Where(sq.Eq{"a.kind": f.Kind})
|
||||||
|
|
||||||
|
switch f.Kind {
|
||||||
|
case types.PageAttachment:
|
||||||
|
// @todo implement filtering by page
|
||||||
|
err = errors.New("filtering by page not implemented")
|
||||||
|
return
|
||||||
|
case types.RecordAttachment:
|
||||||
|
query = query.
|
||||||
|
Join("crm_record_value AS v ON (v.ref = a.id)")
|
||||||
|
|
||||||
|
if f.ModuleID > 0 {
|
||||||
|
query = query.
|
||||||
|
Join("crm_record AS r ON (r.id = v.record_id)").
|
||||||
|
Where(sq.Eq{"r.module_id": f.ModuleID})
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.RecordID > 0 {
|
||||||
|
query = query.Where(sq.Eq{"v.record_id": f.RecordID})
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.FieldName != "" {
|
||||||
|
query = query.Where(sq.Eq{"v.name": f.FieldName})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Filter != "" {
|
||||||
|
err = errors.New("filtering by filter not implemented")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble SQL for counting (includes only where)
|
||||||
|
count := query.Column("COUNT(*)")
|
||||||
|
if sqlSelect, argsSelect, err := count.ToSql(); err != nil {
|
||||||
|
return set, f, err
|
||||||
|
} else {
|
||||||
|
// Execute count query.
|
||||||
|
if err := r.db().Get(&f.Count, sqlSelect, argsSelect...); err != nil {
|
||||||
|
return set, f, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return empty response if count of records is zero.
|
||||||
|
if f.Count == 0 {
|
||||||
|
return set, f, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble SQL for fetching attachments (where + sorting + paging)...
|
||||||
|
query = query.
|
||||||
|
Column(sqlAttachmentColumns).
|
||||||
|
Limit(uint64(f.PerPage)).
|
||||||
|
Offset(uint64(f.Page * f.PerPage))
|
||||||
|
|
||||||
|
if sqlSelect, argsSelect, err := query.ToSql(); err != nil {
|
||||||
|
return set, f, err
|
||||||
|
} else {
|
||||||
|
return set, f, r.db().Select(&set, sqlSelect, argsSelect...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *attachment) Create(mod *types.Attachment) (*types.Attachment, error) {
|
||||||
|
if mod.ID == 0 {
|
||||||
|
mod.ID = factory.Sonyflake.NextID()
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.CreatedAt = time.Now()
|
||||||
|
|
||||||
|
return mod, r.db().Insert("crm_attachment", mod)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *attachment) DeleteByID(id uint64) error {
|
||||||
|
_, err := r.db().Exec("UPDATE crm_attachment SET deleted_at = NOW() WHERE id = ?", id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
53
crm/rest/attachment.go
Normal file
53
crm/rest/attachment.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/rest/request"
|
||||||
|
"github.com/crusttech/crust/crm/service"
|
||||||
|
"github.com/crusttech/crust/crm/types"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = errors.Wrap
|
||||||
|
|
||||||
|
type Attachment struct {
|
||||||
|
attachment service.AttachmentService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Attachment) New() *Attachment {
|
||||||
|
return &Attachment{attachment: service.DefaultAttachment}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachments returns list of all files attached to records
|
||||||
|
func (ctrl *Attachment) List(ctx context.Context, r *request.AttachmentList) (interface{}, error) {
|
||||||
|
f := types.AttachmentFilter{
|
||||||
|
Kind: types.RecordAttachment,
|
||||||
|
ModuleID: r.ModuleID,
|
||||||
|
RecordID: r.RecordID,
|
||||||
|
FieldName: r.FieldName,
|
||||||
|
// Filter: r.Filter,
|
||||||
|
PerPage: r.PerPage,
|
||||||
|
Page: r.Page,
|
||||||
|
// Sort: r.Sort,
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeRecordAttachmentSetPayload(ctrl.attachment.Find(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Attachment) Details(ctx context.Context, r *request.AttachmentDetails) (interface{}, error) {
|
||||||
|
if a, err := ctrl.attachment.FindByID(r.AttachmentID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return makeAttachmentPayload(a), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Attachment) Original(ctx context.Context, r *request.AttachmentOriginal) (interface{}, error) {
|
||||||
|
return loadAttachedFile(ctrl.attachment, r.AttachmentID, false, r.Download)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Attachment) Preview(ctx context.Context, r *request.AttachmentPreview) (interface{}, error) {
|
||||||
|
return loadAttachedFile(ctrl.attachment, r.AttachmentID, true, false)
|
||||||
|
}
|
||||||
119
crm/rest/attachment_custom.go
Normal file
119
crm/rest/attachment_custom.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/rest/handlers"
|
||||||
|
"github.com/crusttech/crust/crm/service"
|
||||||
|
"github.com/crusttech/crust/crm/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
attachmentPayload struct {
|
||||||
|
ID uint64 `json:"attachmentID,string"`
|
||||||
|
OwnerID uint64 `json:"ownerID,string"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
PreviewUrl string `json:"previewUrl,omitempty"`
|
||||||
|
Meta interface{} `json:"meta"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||||
|
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
file struct {
|
||||||
|
*types.Attachment
|
||||||
|
content io.ReadSeeker
|
||||||
|
download bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAttachedFile(svc service.AttachmentService, ID uint64, preview, download bool) (handlers.Downloadable, error) {
|
||||||
|
rval := &file{download: download}
|
||||||
|
|
||||||
|
if att, err := svc.FindByID(ID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
rval.Attachment = att
|
||||||
|
if preview {
|
||||||
|
spew.Dump(att)
|
||||||
|
rval.content, err = svc.OpenPreview(att)
|
||||||
|
} else {
|
||||||
|
rval.content, err = svc.OpenOriginal(att)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeAttachmentPayload(a *types.Attachment) *attachmentPayload {
|
||||||
|
if a == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var preview string
|
||||||
|
var baseURL = fmt.Sprintf("/attachment/%s/%d/", a.Kind, a.ID)
|
||||||
|
|
||||||
|
if a.Meta.Preview != nil {
|
||||||
|
var ext = a.Meta.Preview.Extension
|
||||||
|
if ext == "" {
|
||||||
|
ext = "jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
preview = baseURL + fmt.Sprintf("preview.%s", ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &attachmentPayload{
|
||||||
|
ID: a.ID,
|
||||||
|
OwnerID: a.OwnerID,
|
||||||
|
Url: baseURL + fmt.Sprintf("original/%s", url.PathEscape(a.Name)),
|
||||||
|
PreviewUrl: preview,
|
||||||
|
Meta: a.Meta,
|
||||||
|
Name: a.Name,
|
||||||
|
CreatedAt: a.CreatedAt,
|
||||||
|
UpdatedAt: a.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRecordAttachmentSetPayload(aa types.AttachmentSet, meta types.AttachmentFilter, err error) (map[string]interface{}, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pp := make([]*attachmentPayload, len(aa))
|
||||||
|
for i := range aa {
|
||||||
|
pp[i] = makeAttachmentPayload(aa[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
rval := map[string]interface{}{"meta": meta, "attachments": pp}
|
||||||
|
|
||||||
|
return rval, err
|
||||||
|
}
|
||||||
87
crm/rest/handlers/attachment.go
Normal file
87
crm/rest/handlers/attachment.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
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 `attachment.go`, `attachment.util.go` or `attachment_test.go` to
|
||||||
|
implement your API calls, helper functions and tests. The file `attachment.go`
|
||||||
|
is only generated the first time, and will not be overwritten if it exists.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/titpetric/factory/resputil"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/rest/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Internal API interface
|
||||||
|
type AttachmentAPI interface {
|
||||||
|
List(context.Context, *request.AttachmentList) (interface{}, error)
|
||||||
|
Details(context.Context, *request.AttachmentDetails) (interface{}, error)
|
||||||
|
Original(context.Context, *request.AttachmentOriginal) (interface{}, error)
|
||||||
|
Preview(context.Context, *request.AttachmentPreview) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP API interface
|
||||||
|
type Attachment struct {
|
||||||
|
List func(http.ResponseWriter, *http.Request)
|
||||||
|
Details func(http.ResponseWriter, *http.Request)
|
||||||
|
Original func(http.ResponseWriter, *http.Request)
|
||||||
|
Preview func(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttachment(ah AttachmentAPI) *Attachment {
|
||||||
|
return &Attachment{
|
||||||
|
List: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewAttachmentList()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return ah.List(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Details: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewAttachmentDetails()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return ah.Details(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Original: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewAttachmentOriginal()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return ah.Original(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Preview: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewAttachmentPreview()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return ah.Preview(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ah *Attachment) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Use(middlewares...)
|
||||||
|
r.Route("/attachment/{kind}", func(r chi.Router) {
|
||||||
|
r.Get("/", ah.List)
|
||||||
|
r.Get("/{attachmentID}", ah.Details)
|
||||||
|
r.Get("/{attachmentID}/original/{name}", ah.Original)
|
||||||
|
r.Get("/{attachmentID}/preview.{ext}", ah.Preview)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
61
crm/rest/handlers/attachment_custom.go
Normal file
61
crm/rest/handlers/attachment_custom.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/rest/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Downloadable interface {
|
||||||
|
Name() string
|
||||||
|
Download() bool
|
||||||
|
ModTime() time.Time
|
||||||
|
Content() io.ReadSeeker
|
||||||
|
Valid() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttachmentDownloadable(ctrl AttachmentAPI) *Attachment {
|
||||||
|
h := NewAttachment(ctrl)
|
||||||
|
h.Original = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewAttachmentOriginal()
|
||||||
|
params.Fill(r)
|
||||||
|
|
||||||
|
f, err := ctrl.Original(r.Context(), params)
|
||||||
|
serveFile(f, err, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Preview = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewAttachmentPreview()
|
||||||
|
params.Fill(r)
|
||||||
|
|
||||||
|
f, err := ctrl.Preview(r.Context(), params)
|
||||||
|
serveFile(f, err, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveFile(f interface{}, err error, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
} else if dl, ok := f.(Downloadable); ok {
|
||||||
|
if !dl.Valid() {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
} else {
|
||||||
|
if dl.Download() {
|
||||||
|
w.Header().Add("Content-Disposition", "attachment; filename="+url.QueryEscape(dl.Name()))
|
||||||
|
} else {
|
||||||
|
w.Header().Add("Content-Disposition", "inline; filename="+url.QueryEscape(dl.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
http.ServeContent(w, r, dl.Name(), dl.ModTime(), dl.Content())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Got incompatible type from controller", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,27 +32,15 @@ type ModuleAPI interface {
|
|||||||
Read(context.Context, *request.ModuleRead) (interface{}, error)
|
Read(context.Context, *request.ModuleRead) (interface{}, error)
|
||||||
Update(context.Context, *request.ModuleUpdate) (interface{}, error)
|
Update(context.Context, *request.ModuleUpdate) (interface{}, error)
|
||||||
Delete(context.Context, *request.ModuleDelete) (interface{}, error)
|
Delete(context.Context, *request.ModuleDelete) (interface{}, error)
|
||||||
RecordReport(context.Context, *request.ModuleRecordReport) (interface{}, error)
|
|
||||||
RecordList(context.Context, *request.ModuleRecordList) (interface{}, error)
|
|
||||||
RecordCreate(context.Context, *request.ModuleRecordCreate) (interface{}, error)
|
|
||||||
RecordRead(context.Context, *request.ModuleRecordRead) (interface{}, error)
|
|
||||||
RecordUpdate(context.Context, *request.ModuleRecordUpdate) (interface{}, error)
|
|
||||||
RecordDelete(context.Context, *request.ModuleRecordDelete) (interface{}, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP API interface
|
// HTTP API interface
|
||||||
type Module struct {
|
type Module struct {
|
||||||
List func(http.ResponseWriter, *http.Request)
|
List func(http.ResponseWriter, *http.Request)
|
||||||
Create func(http.ResponseWriter, *http.Request)
|
Create func(http.ResponseWriter, *http.Request)
|
||||||
Read func(http.ResponseWriter, *http.Request)
|
Read func(http.ResponseWriter, *http.Request)
|
||||||
Update func(http.ResponseWriter, *http.Request)
|
Update func(http.ResponseWriter, *http.Request)
|
||||||
Delete func(http.ResponseWriter, *http.Request)
|
Delete func(http.ResponseWriter, *http.Request)
|
||||||
RecordReport func(http.ResponseWriter, *http.Request)
|
|
||||||
RecordList func(http.ResponseWriter, *http.Request)
|
|
||||||
RecordCreate func(http.ResponseWriter, *http.Request)
|
|
||||||
RecordRead func(http.ResponseWriter, *http.Request)
|
|
||||||
RecordUpdate func(http.ResponseWriter, *http.Request)
|
|
||||||
RecordDelete func(http.ResponseWriter, *http.Request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewModule(mh ModuleAPI) *Module {
|
func NewModule(mh ModuleAPI) *Module {
|
||||||
@@ -92,48 +80,6 @@ func NewModule(mh ModuleAPI) *Module {
|
|||||||
return mh.Delete(r.Context(), params)
|
return mh.Delete(r.Context(), params)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
RecordReport: func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
params := request.NewModuleRecordReport()
|
|
||||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
|
||||||
return mh.RecordReport(r.Context(), params)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
RecordList: func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
params := request.NewModuleRecordList()
|
|
||||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
|
||||||
return mh.RecordList(r.Context(), params)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
RecordCreate: func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
params := request.NewModuleRecordCreate()
|
|
||||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
|
||||||
return mh.RecordCreate(r.Context(), params)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
RecordRead: func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
params := request.NewModuleRecordRead()
|
|
||||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
|
||||||
return mh.RecordRead(r.Context(), params)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
RecordUpdate: func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
params := request.NewModuleRecordUpdate()
|
|
||||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
|
||||||
return mh.RecordUpdate(r.Context(), params)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
RecordDelete: func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
params := request.NewModuleRecordDelete()
|
|
||||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
|
||||||
return mh.RecordDelete(r.Context(), params)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,12 +92,6 @@ func (mh *Module) MountRoutes(r chi.Router, middlewares ...func(http.Handler) ht
|
|||||||
r.Get("/{moduleID}", mh.Read)
|
r.Get("/{moduleID}", mh.Read)
|
||||||
r.Post("/{moduleID}", mh.Update)
|
r.Post("/{moduleID}", mh.Update)
|
||||||
r.Delete("/{moduleID}", mh.Delete)
|
r.Delete("/{moduleID}", mh.Delete)
|
||||||
r.Get("/{moduleID}/report", mh.RecordReport)
|
|
||||||
r.Get("/{moduleID}/record", mh.RecordList)
|
|
||||||
r.Post("/{moduleID}/record", mh.RecordCreate)
|
|
||||||
r.Get("/{moduleID}/record/{recordID}", mh.RecordRead)
|
|
||||||
r.Post("/{moduleID}/record/{recordID}", mh.RecordUpdate)
|
|
||||||
r.Delete("/{moduleID}/record/{recordID}", mh.RecordDelete)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type PageAPI interface {
|
|||||||
Update(context.Context, *request.PageUpdate) (interface{}, error)
|
Update(context.Context, *request.PageUpdate) (interface{}, error)
|
||||||
Reorder(context.Context, *request.PageReorder) (interface{}, error)
|
Reorder(context.Context, *request.PageReorder) (interface{}, error)
|
||||||
Delete(context.Context, *request.PageDelete) (interface{}, error)
|
Delete(context.Context, *request.PageDelete) (interface{}, error)
|
||||||
|
Upload(context.Context, *request.PageUpload) (interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP API interface
|
// HTTP API interface
|
||||||
@@ -45,6 +46,7 @@ type Page struct {
|
|||||||
Update func(http.ResponseWriter, *http.Request)
|
Update func(http.ResponseWriter, *http.Request)
|
||||||
Reorder func(http.ResponseWriter, *http.Request)
|
Reorder func(http.ResponseWriter, *http.Request)
|
||||||
Delete func(http.ResponseWriter, *http.Request)
|
Delete func(http.ResponseWriter, *http.Request)
|
||||||
|
Upload func(http.ResponseWriter, *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPage(ph PageAPI) *Page {
|
func NewPage(ph PageAPI) *Page {
|
||||||
@@ -98,6 +100,13 @@ func NewPage(ph PageAPI) *Page {
|
|||||||
return ph.Delete(r.Context(), params)
|
return ph.Delete(r.Context(), params)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
Upload: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewPageUpload()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return ph.Upload(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +121,7 @@ func (ph *Page) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http
|
|||||||
r.Post("/{pageID}", ph.Update)
|
r.Post("/{pageID}", ph.Update)
|
||||||
r.Post("/{selfID}/reorder", ph.Reorder)
|
r.Post("/{selfID}/reorder", ph.Reorder)
|
||||||
r.Delete("/{pageID}", ph.Delete)
|
r.Delete("/{pageID}", ph.Delete)
|
||||||
|
r.Post("/{pageID}/attachment", ph.Upload)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
117
crm/rest/handlers/record.go
Normal file
117
crm/rest/handlers/record.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
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 `record.go`, `record.util.go` or `record_test.go` to
|
||||||
|
implement your API calls, helper functions and tests. The file `record.go`
|
||||||
|
is only generated the first time, and will not be overwritten if it exists.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/titpetric/factory/resputil"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/rest/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Internal API interface
|
||||||
|
type RecordAPI interface {
|
||||||
|
Report(context.Context, *request.RecordReport) (interface{}, error)
|
||||||
|
List(context.Context, *request.RecordList) (interface{}, error)
|
||||||
|
Create(context.Context, *request.RecordCreate) (interface{}, error)
|
||||||
|
Read(context.Context, *request.RecordRead) (interface{}, error)
|
||||||
|
Update(context.Context, *request.RecordUpdate) (interface{}, error)
|
||||||
|
Delete(context.Context, *request.RecordDelete) (interface{}, error)
|
||||||
|
Upload(context.Context, *request.RecordUpload) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP API interface
|
||||||
|
type Record struct {
|
||||||
|
Report func(http.ResponseWriter, *http.Request)
|
||||||
|
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)
|
||||||
|
Upload func(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecord(rh RecordAPI) *Record {
|
||||||
|
return &Record{
|
||||||
|
Report: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewRecordReport()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return rh.Report(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
List: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewRecordList()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return rh.List(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Create: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewRecordCreate()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return rh.Create(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Read: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewRecordRead()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return rh.Read(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewRecordUpdate()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return rh.Update(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Delete: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewRecordDelete()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return rh.Delete(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Upload: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
params := request.NewRecordUpload()
|
||||||
|
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||||
|
return rh.Upload(r.Context(), params)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rh *Record) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Use(middlewares...)
|
||||||
|
r.Route("/module/{moduleID}", func(r chi.Router) {
|
||||||
|
r.Get("/report", rh.Report)
|
||||||
|
r.Get("/record", rh.List)
|
||||||
|
r.Post("/record", rh.Create)
|
||||||
|
r.Get("/record/{recordID}", rh.Read)
|
||||||
|
r.Post("/record/{recordID}", rh.Update)
|
||||||
|
r.Delete("/record/{recordID}", rh.Delete)
|
||||||
|
r.Post("/record/{recordID}/{fieldName}/attachment", rh.Upload)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -24,60 +24,33 @@ func (Module) New() *Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Module) List(ctx context.Context, r *request.ModuleList) (interface{}, error) {
|
func (ctrl *Module) List(ctx context.Context, r *request.ModuleList) (interface{}, error) {
|
||||||
return s.module.With(ctx).Find()
|
return ctrl.module.With(ctx).Find()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Module) Read(ctx context.Context, r *request.ModuleRead) (interface{}, error) {
|
func (ctrl *Module) Read(ctx context.Context, r *request.ModuleRead) (interface{}, error) {
|
||||||
return s.module.With(ctx).FindByID(r.ModuleID)
|
return ctrl.module.With(ctx).FindByID(r.ModuleID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Module) Delete(ctx context.Context, r *request.ModuleDelete) (interface{}, error) {
|
func (ctrl *Module) Delete(ctx context.Context, r *request.ModuleDelete) (interface{}, error) {
|
||||||
return resputil.OK(), s.module.With(ctx).DeleteByID(r.ModuleID)
|
return resputil.OK(), ctrl.module.With(ctx).DeleteByID(r.ModuleID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Module) Create(ctx context.Context, r *request.ModuleCreate) (interface{}, error) {
|
func (ctrl *Module) Create(ctx context.Context, r *request.ModuleCreate) (interface{}, error) {
|
||||||
item := &types.Module{
|
item := &types.Module{
|
||||||
Name: r.Name,
|
Name: r.Name,
|
||||||
Fields: r.Fields,
|
Fields: r.Fields,
|
||||||
Meta: r.Meta,
|
Meta: r.Meta,
|
||||||
}
|
}
|
||||||
return s.module.With(ctx).Create(item)
|
return ctrl.module.With(ctx).Create(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Module) Update(ctx context.Context, r *request.ModuleUpdate) (interface{}, error) {
|
func (ctrl *Module) Update(ctx context.Context, r *request.ModuleUpdate) (interface{}, error) {
|
||||||
item := &types.Module{
|
item := &types.Module{
|
||||||
ID: r.ModuleID,
|
ID: r.ModuleID,
|
||||||
Name: r.Name,
|
Name: r.Name,
|
||||||
Fields: r.Fields,
|
Fields: r.Fields,
|
||||||
Meta: r.Meta,
|
Meta: r.Meta,
|
||||||
}
|
}
|
||||||
return s.module.With(ctx).Update(item)
|
return ctrl.module.With(ctx).Update(item)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Module) RecordReport(ctx context.Context, r *request.ModuleRecordReport) (interface{}, error) {
|
|
||||||
return s.record.With(ctx).Report(r.ModuleID, r.Metrics, r.Dimensions, r.Filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Module) RecordList(ctx context.Context, r *request.ModuleRecordList) (interface{}, error) {
|
|
||||||
return s.record.With(ctx).Find(r.ModuleID, r.Filter, r.Sort, r.Page, r.PerPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Module) RecordRead(ctx context.Context, r *request.ModuleRecordRead) (interface{}, error) {
|
|
||||||
return s.record.With(ctx).FindByID(r.RecordID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Module) RecordCreate(ctx context.Context, r *request.ModuleRecordCreate) (interface{}, error) {
|
|
||||||
return s.record.With(ctx).Create(&types.Record{ModuleID: r.ModuleID, Values: r.Values})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Module) RecordUpdate(ctx context.Context, r *request.ModuleRecordUpdate) (interface{}, error) {
|
|
||||||
return s.record.With(ctx).Update(&types.Record{
|
|
||||||
ID: r.RecordID,
|
|
||||||
ModuleID: r.ModuleID,
|
|
||||||
Values: r.Values})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Module) RecordDelete(ctx context.Context, r *request.ModuleRecordDelete) (interface{}, error) {
|
|
||||||
return resputil.OK(), s.record.With(ctx).DeleteByID(r.RecordID)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
Page struct {
|
Page struct {
|
||||||
page service.PageService
|
page service.PageService
|
||||||
|
attachment service.AttachmentService
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (Page) New() *Page {
|
func (Page) New() *Page {
|
||||||
return &Page{
|
return &Page{
|
||||||
page: service.DefaultPage,
|
page: service.DefaultPage,
|
||||||
|
attachment: service.DefaultAttachment,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,3 +73,26 @@ func (ctrl *Page) Update(ctx context.Context, r *request.PageUpdate) (interface{
|
|||||||
func (ctrl *Page) Delete(ctx context.Context, r *request.PageDelete) (interface{}, error) {
|
func (ctrl *Page) Delete(ctx context.Context, r *request.PageDelete) (interface{}, error) {
|
||||||
return resputil.OK(), ctrl.page.With(ctx).DeleteByID(r.PageID)
|
return resputil.OK(), ctrl.page.With(ctx).DeleteByID(r.PageID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctrl *Page) Upload(ctx context.Context, r *request.PageUpload) (interface{}, error) {
|
||||||
|
// @todo [SECURITY] check if attachments can be added to this page
|
||||||
|
file, err := r.Upload.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
a, err := ctrl.attachment.With(ctx).CreatePageAttachment(
|
||||||
|
r.Upload.Filename,
|
||||||
|
r.Upload.Size,
|
||||||
|
file,
|
||||||
|
r.PageID,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeAttachmentPayload(a), nil
|
||||||
|
}
|
||||||
|
|||||||
67
crm/rest/pageattachment.go_
Normal file
67
crm/rest/pageattachment.go_
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/rest/request"
|
||||||
|
"github.com/crusttech/crust/crm/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = errors.Wrap
|
||||||
|
|
||||||
|
type PageAttachment struct {
|
||||||
|
att service.AttachmentService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (PageAttachment) New() *PageAttachment {
|
||||||
|
return &PageAttachment{att: service.DefaultAttachment}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *PageAttachment) Upload(ctx context.Context, r *request.PageAttachmentUpload) (interface{}, error) {
|
||||||
|
// @todo [SECURITY] check if attachments can be added to this page
|
||||||
|
file, err := r.Upload.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
a, err := ctrl.att.With(ctx).CreatePageAttachment(
|
||||||
|
r.Upload.Filename,
|
||||||
|
r.Upload.Size,
|
||||||
|
file,
|
||||||
|
r.PageID,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseURL := fmt.Sprintf("/page/%d/attachment", r.PageID)
|
||||||
|
return makeAttachmentPayload(baseURL, a), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *PageAttachment) Details(ctx context.Context, r *request.PageAttachmentDetails) (interface{}, error) {
|
||||||
|
// @todo [SECURITY] check if page can be accessed
|
||||||
|
// @todo [SECURITY] test any of the page blocks contain this attachment, return 404 if not
|
||||||
|
if a, err := ctrl.att.FindByID(r.AttachmentID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return makePageAttachmentPayload(r.PageID, a), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *PageAttachment) Original(ctx context.Context, r *request.PageAttachmentOriginal) (interface{}, error) {
|
||||||
|
// @todo [SECURITY] check if page can be accessed
|
||||||
|
// @todo [SECURITY] test any of the page blocks contain this attachment, return 404 if not
|
||||||
|
return loadAttachedFile(ctrl.att, r.AttachmentID, false, r.Download)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *PageAttachment) Preview(ctx context.Context, r *request.PageAttachmentPreview) (interface{}, error) {
|
||||||
|
// @todo [SECURITY] check if page can be accessed
|
||||||
|
// @todo [SECURITY] test any of the page blocks contain this attachment, return 404 if not
|
||||||
|
return loadAttachedFile(ctrl.att, r.AttachmentID, true, false)
|
||||||
|
}
|
||||||
79
crm/rest/record.go
Normal file
79
crm/rest/record.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/titpetric/factory/resputil"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/rest/request"
|
||||||
|
"github.com/crusttech/crust/crm/service"
|
||||||
|
"github.com/crusttech/crust/crm/types"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = errors.Wrap
|
||||||
|
|
||||||
|
type Record struct {
|
||||||
|
record service.RecordService
|
||||||
|
attachment service.AttachmentService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Record) New() *Record {
|
||||||
|
return &Record{
|
||||||
|
record: service.DefaultRecord,
|
||||||
|
attachment: service.DefaultAttachment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Record) Report(ctx context.Context, r *request.RecordReport) (interface{}, error) {
|
||||||
|
return ctrl.record.With(ctx).Report(r.ModuleID, r.Metrics, r.Dimensions, r.Filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Record) List(ctx context.Context, r *request.RecordList) (interface{}, error) {
|
||||||
|
return ctrl.record.With(ctx).Find(r.ModuleID, r.Filter, r.Sort, r.Page, r.PerPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Record) Read(ctx context.Context, r *request.RecordRead) (interface{}, error) {
|
||||||
|
return ctrl.record.With(ctx).FindByID(r.RecordID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Record) Create(ctx context.Context, r *request.RecordCreate) (interface{}, error) {
|
||||||
|
return ctrl.record.With(ctx).Create(&types.Record{ModuleID: r.ModuleID, Values: r.Values})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Record) Update(ctx context.Context, r *request.RecordUpdate) (interface{}, error) {
|
||||||
|
return ctrl.record.With(ctx).Update(&types.Record{
|
||||||
|
ID: r.RecordID,
|
||||||
|
ModuleID: r.ModuleID,
|
||||||
|
Values: r.Values})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Record) Delete(ctx context.Context, r *request.RecordDelete) (interface{}, error) {
|
||||||
|
return resputil.OK(), ctrl.record.With(ctx).DeleteByID(r.RecordID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *Record) Upload(ctx context.Context, r *request.RecordUpload) (interface{}, error) {
|
||||||
|
// @todo [SECURITY] check if attachments can be added to this page
|
||||||
|
file, err := r.Upload.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
a, err := ctrl.attachment.With(ctx).CreateRecordAttachment(
|
||||||
|
r.Upload.Filename,
|
||||||
|
r.Upload.Size,
|
||||||
|
file,
|
||||||
|
r.ModuleID,
|
||||||
|
r.RecordID,
|
||||||
|
r.FieldName,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeAttachmentPayload(a), nil
|
||||||
|
}
|
||||||
69
crm/rest/recordattachment.go_
Normal file
69
crm/rest/recordattachment.go_
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/rest/request"
|
||||||
|
"github.com/crusttech/crust/crm/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = errors.Wrap
|
||||||
|
|
||||||
|
type RecordAttachment struct {
|
||||||
|
att service.AttachmentService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RecordAttachment) New() *RecordAttachment {
|
||||||
|
return &RecordAttachment{att: service.DefaultAttachment}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *RecordAttachment) Upload(ctx context.Context, r *request.RecordAttachmentUpload) (interface{}, error) {
|
||||||
|
// @todo [SECURITY] check if attachments can be added to this page
|
||||||
|
file, err := r.Upload.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
a, err := ctrl.att.With(ctx).CreateRecordAttachment(
|
||||||
|
r.Upload.Filename,
|
||||||
|
r.Upload.Size,
|
||||||
|
file,
|
||||||
|
r.ModuleID,
|
||||||
|
r.RecordID,
|
||||||
|
r.FieldName,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseURL := fmt.Sprintf("/module/%d/record/%d/attachment/%s/", r.ModuleID, r.RecordID, r.FieldName)
|
||||||
|
return makeAttachmentPayload(baseURL, a), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *RecordAttachment) Details(ctx context.Context, r *request.RecordAttachmentDetails) (interface{}, error) {
|
||||||
|
// @todo [security] check if record can be accessed
|
||||||
|
// @todo [SECURITY] test if module/record/field has this attachment, return 404 if not
|
||||||
|
if a, err := ctrl.att.FindByID(r.AttachmentID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return makeRecordAttachmentPayload(r.ModuleID, r.RecordID, r.FieldName, a), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *RecordAttachment) Original(ctx context.Context, r *request.RecordAttachmentOriginal) (interface{}, error) {
|
||||||
|
// @todo [security] check if record can be accessed
|
||||||
|
// @todo [SECURITY] test if module/record/field has this attachment, return 404 if not
|
||||||
|
return loadAttachedFile(ctrl.att, r.AttachmentID, false, r.Download)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctrl *RecordAttachment) Preview(ctx context.Context, r *request.RecordAttachmentPreview) (interface{}, error) {
|
||||||
|
// @todo [security] check if record can be accessed
|
||||||
|
// @todo [SECURITY] test if module/record/field has this attachment, return 404 if not
|
||||||
|
return loadAttachedFile(ctrl.att, r.AttachmentID, true, false)
|
||||||
|
}
|
||||||
247
crm/rest/request/attachment.go
Normal file
247
crm/rest/request/attachment.go
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
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 `attachment.go`, `attachment.util.go` or `attachment_test.go` to
|
||||||
|
implement your API calls, helper functions and tests. The file `attachment.go`
|
||||||
|
is only generated the first time, and will not be overwritten if it exists.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = chi.URLParam
|
||||||
|
var _ = multipart.FileHeader{}
|
||||||
|
|
||||||
|
// Attachment list request parameters
|
||||||
|
type AttachmentList struct {
|
||||||
|
PageID uint64 `json:",string"`
|
||||||
|
ModuleID uint64 `json:",string"`
|
||||||
|
RecordID uint64 `json:",string"`
|
||||||
|
FieldName string
|
||||||
|
Page uint
|
||||||
|
PerPage uint
|
||||||
|
Kind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttachmentList() *AttachmentList {
|
||||||
|
return &AttachmentList{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aReq *AttachmentList) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(aReq)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
err = nil
|
||||||
|
case err != nil:
|
||||||
|
return errors.Wrap(err, "error parsing http request body")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
get := map[string]string{}
|
||||||
|
post := map[string]string{}
|
||||||
|
urlQuery := r.URL.Query()
|
||||||
|
for name, param := range urlQuery {
|
||||||
|
get[name] = string(param[0])
|
||||||
|
}
|
||||||
|
postVars := r.Form
|
||||||
|
for name, param := range postVars {
|
||||||
|
post[name] = string(param[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := get["pageID"]; ok {
|
||||||
|
|
||||||
|
aReq.PageID = parseUInt64(val)
|
||||||
|
}
|
||||||
|
if val, ok := get["moduleID"]; ok {
|
||||||
|
|
||||||
|
aReq.ModuleID = parseUInt64(val)
|
||||||
|
}
|
||||||
|
if val, ok := get["recordID"]; ok {
|
||||||
|
|
||||||
|
aReq.RecordID = parseUInt64(val)
|
||||||
|
}
|
||||||
|
if val, ok := get["fieldName"]; ok {
|
||||||
|
|
||||||
|
aReq.FieldName = val
|
||||||
|
}
|
||||||
|
if val, ok := get["page"]; ok {
|
||||||
|
|
||||||
|
aReq.Page = parseUint(val)
|
||||||
|
}
|
||||||
|
if val, ok := get["perPage"]; ok {
|
||||||
|
|
||||||
|
aReq.PerPage = parseUint(val)
|
||||||
|
}
|
||||||
|
aReq.Kind = chi.URLParam(r, "kind")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewAttachmentList()
|
||||||
|
|
||||||
|
// Attachment details request parameters
|
||||||
|
type AttachmentDetails struct {
|
||||||
|
AttachmentID uint64 `json:",string"`
|
||||||
|
Kind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttachmentDetails() *AttachmentDetails {
|
||||||
|
return &AttachmentDetails{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aReq *AttachmentDetails) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(aReq)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
err = nil
|
||||||
|
case err != nil:
|
||||||
|
return errors.Wrap(err, "error parsing http request body")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
get := map[string]string{}
|
||||||
|
post := map[string]string{}
|
||||||
|
urlQuery := r.URL.Query()
|
||||||
|
for name, param := range urlQuery {
|
||||||
|
get[name] = string(param[0])
|
||||||
|
}
|
||||||
|
postVars := r.Form
|
||||||
|
for name, param := range postVars {
|
||||||
|
post[name] = string(param[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
aReq.AttachmentID = parseUInt64(chi.URLParam(r, "attachmentID"))
|
||||||
|
aReq.Kind = chi.URLParam(r, "kind")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewAttachmentDetails()
|
||||||
|
|
||||||
|
// Attachment original request parameters
|
||||||
|
type AttachmentOriginal struct {
|
||||||
|
Download bool
|
||||||
|
AttachmentID uint64 `json:",string"`
|
||||||
|
Name string
|
||||||
|
Kind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttachmentOriginal() *AttachmentOriginal {
|
||||||
|
return &AttachmentOriginal{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aReq *AttachmentOriginal) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(aReq)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
err = nil
|
||||||
|
case err != nil:
|
||||||
|
return errors.Wrap(err, "error parsing http request body")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
get := map[string]string{}
|
||||||
|
post := map[string]string{}
|
||||||
|
urlQuery := r.URL.Query()
|
||||||
|
for name, param := range urlQuery {
|
||||||
|
get[name] = string(param[0])
|
||||||
|
}
|
||||||
|
postVars := r.Form
|
||||||
|
for name, param := range postVars {
|
||||||
|
post[name] = string(param[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := get["download"]; ok {
|
||||||
|
|
||||||
|
aReq.Download = parseBool(val)
|
||||||
|
}
|
||||||
|
aReq.AttachmentID = parseUInt64(chi.URLParam(r, "attachmentID"))
|
||||||
|
aReq.Name = chi.URLParam(r, "name")
|
||||||
|
aReq.Kind = chi.URLParam(r, "kind")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewAttachmentOriginal()
|
||||||
|
|
||||||
|
// Attachment preview request parameters
|
||||||
|
type AttachmentPreview struct {
|
||||||
|
AttachmentID uint64 `json:",string"`
|
||||||
|
Ext string
|
||||||
|
Kind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttachmentPreview() *AttachmentPreview {
|
||||||
|
return &AttachmentPreview{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aReq *AttachmentPreview) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(aReq)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
err = nil
|
||||||
|
case err != nil:
|
||||||
|
return errors.Wrap(err, "error parsing http request body")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
get := map[string]string{}
|
||||||
|
post := map[string]string{}
|
||||||
|
urlQuery := r.URL.Query()
|
||||||
|
for name, param := range urlQuery {
|
||||||
|
get[name] = string(param[0])
|
||||||
|
}
|
||||||
|
postVars := r.Form
|
||||||
|
for name, param := range postVars {
|
||||||
|
post[name] = string(param[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
aReq.AttachmentID = parseUInt64(chi.URLParam(r, "attachmentID"))
|
||||||
|
aReq.Ext = chi.URLParam(r, "ext")
|
||||||
|
aReq.Kind = chi.URLParam(r, "kind")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewAttachmentPreview()
|
||||||
@@ -175,67 +175,6 @@ func (mReq *ModuleRead) Fill(r *http.Request) (err error) {
|
|||||||
|
|
||||||
var _ RequestFiller = NewModuleRead()
|
var _ RequestFiller = NewModuleRead()
|
||||||
|
|
||||||
// Module attachments request parameters
|
|
||||||
type ModuleAttachments struct {
|
|
||||||
Filter string
|
|
||||||
Page int
|
|
||||||
PerPage int
|
|
||||||
Sort string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewModuleAttachments() *ModuleAttachments {
|
|
||||||
return &ModuleAttachments{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mReq *ModuleAttachments) Fill(r *http.Request) (err error) {
|
|
||||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
|
||||||
err = json.NewDecoder(r.Body).Decode(mReq)
|
|
||||||
|
|
||||||
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["filter"]; ok {
|
|
||||||
|
|
||||||
mReq.Filter = val
|
|
||||||
}
|
|
||||||
if val, ok := get["page"]; ok {
|
|
||||||
|
|
||||||
mReq.Page = parseInt(val)
|
|
||||||
}
|
|
||||||
if val, ok := get["perPage"]; ok {
|
|
||||||
|
|
||||||
mReq.PerPage = parseInt(val)
|
|
||||||
}
|
|
||||||
if val, ok := get["sort"]; ok {
|
|
||||||
|
|
||||||
mReq.Sort = val
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RequestFiller = NewModuleAttachments()
|
|
||||||
|
|
||||||
// Module update request parameters
|
// Module update request parameters
|
||||||
type ModuleUpdate struct {
|
type ModuleUpdate struct {
|
||||||
ModuleID uint64 `json:",string"`
|
ModuleID uint64 `json:",string"`
|
||||||
@@ -334,304 +273,3 @@ func (mReq *ModuleDelete) Fill(r *http.Request) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ RequestFiller = NewModuleDelete()
|
var _ RequestFiller = NewModuleDelete()
|
||||||
|
|
||||||
// Module record/report request parameters
|
|
||||||
type ModuleRecordReport struct {
|
|
||||||
Metrics string
|
|
||||||
Dimensions string
|
|
||||||
Filter string
|
|
||||||
ModuleID uint64 `json:",string"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewModuleRecordReport() *ModuleRecordReport {
|
|
||||||
return &ModuleRecordReport{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mReq *ModuleRecordReport) Fill(r *http.Request) (err error) {
|
|
||||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
|
||||||
err = json.NewDecoder(r.Body).Decode(mReq)
|
|
||||||
|
|
||||||
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["metrics"]; ok {
|
|
||||||
|
|
||||||
mReq.Metrics = val
|
|
||||||
}
|
|
||||||
if val, ok := get["dimensions"]; ok {
|
|
||||||
|
|
||||||
mReq.Dimensions = val
|
|
||||||
}
|
|
||||||
if val, ok := get["filter"]; ok {
|
|
||||||
|
|
||||||
mReq.Filter = val
|
|
||||||
}
|
|
||||||
mReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RequestFiller = NewModuleRecordReport()
|
|
||||||
|
|
||||||
// Module record/list request parameters
|
|
||||||
type ModuleRecordList struct {
|
|
||||||
Filter string
|
|
||||||
Page int
|
|
||||||
PerPage int
|
|
||||||
Sort string
|
|
||||||
ModuleID uint64 `json:",string"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewModuleRecordList() *ModuleRecordList {
|
|
||||||
return &ModuleRecordList{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mReq *ModuleRecordList) Fill(r *http.Request) (err error) {
|
|
||||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
|
||||||
err = json.NewDecoder(r.Body).Decode(mReq)
|
|
||||||
|
|
||||||
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["filter"]; ok {
|
|
||||||
|
|
||||||
mReq.Filter = val
|
|
||||||
}
|
|
||||||
if val, ok := get["page"]; ok {
|
|
||||||
|
|
||||||
mReq.Page = parseInt(val)
|
|
||||||
}
|
|
||||||
if val, ok := get["perPage"]; ok {
|
|
||||||
|
|
||||||
mReq.PerPage = parseInt(val)
|
|
||||||
}
|
|
||||||
if val, ok := get["sort"]; ok {
|
|
||||||
|
|
||||||
mReq.Sort = val
|
|
||||||
}
|
|
||||||
mReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RequestFiller = NewModuleRecordList()
|
|
||||||
|
|
||||||
// Module record/create request parameters
|
|
||||||
type ModuleRecordCreate struct {
|
|
||||||
ModuleID uint64 `json:",string"`
|
|
||||||
Values types.RecordValueSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewModuleRecordCreate() *ModuleRecordCreate {
|
|
||||||
return &ModuleRecordCreate{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mReq *ModuleRecordCreate) Fill(r *http.Request) (err error) {
|
|
||||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
|
||||||
err = json.NewDecoder(r.Body).Decode(mReq)
|
|
||||||
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
|
|
||||||
mReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RequestFiller = NewModuleRecordCreate()
|
|
||||||
|
|
||||||
// Module record/read request parameters
|
|
||||||
type ModuleRecordRead struct {
|
|
||||||
ModuleID uint64 `json:",string"`
|
|
||||||
RecordID uint64 `json:",string"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewModuleRecordRead() *ModuleRecordRead {
|
|
||||||
return &ModuleRecordRead{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mReq *ModuleRecordRead) Fill(r *http.Request) (err error) {
|
|
||||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
|
||||||
err = json.NewDecoder(r.Body).Decode(mReq)
|
|
||||||
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
|
|
||||||
mReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
|
||||||
mReq.RecordID = parseUInt64(chi.URLParam(r, "recordID"))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RequestFiller = NewModuleRecordRead()
|
|
||||||
|
|
||||||
// Module record/update request parameters
|
|
||||||
type ModuleRecordUpdate struct {
|
|
||||||
ModuleID uint64 `json:",string"`
|
|
||||||
RecordID uint64 `json:",string"`
|
|
||||||
Values types.RecordValueSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewModuleRecordUpdate() *ModuleRecordUpdate {
|
|
||||||
return &ModuleRecordUpdate{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mReq *ModuleRecordUpdate) Fill(r *http.Request) (err error) {
|
|
||||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
|
||||||
err = json.NewDecoder(r.Body).Decode(mReq)
|
|
||||||
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
|
|
||||||
mReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
|
||||||
mReq.RecordID = parseUInt64(chi.URLParam(r, "recordID"))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RequestFiller = NewModuleRecordUpdate()
|
|
||||||
|
|
||||||
// Module record/delete request parameters
|
|
||||||
type ModuleRecordDelete struct {
|
|
||||||
ModuleID uint64 `json:",string"`
|
|
||||||
RecordID uint64 `json:",string"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewModuleRecordDelete() *ModuleRecordDelete {
|
|
||||||
return &ModuleRecordDelete{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mReq *ModuleRecordDelete) Fill(r *http.Request) (err error) {
|
|
||||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
|
||||||
err = json.NewDecoder(r.Body).Decode(mReq)
|
|
||||||
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
|
|
||||||
mReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
|
||||||
mReq.RecordID = parseUInt64(chi.URLParam(r, "recordID"))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RequestFiller = NewModuleRecordDelete()
|
|
||||||
|
|||||||
@@ -193,67 +193,6 @@ func (pReq *PageRead) Fill(r *http.Request) (err error) {
|
|||||||
|
|
||||||
var _ RequestFiller = NewPageRead()
|
var _ RequestFiller = NewPageRead()
|
||||||
|
|
||||||
// Page attachments request parameters
|
|
||||||
type PageAttachments struct {
|
|
||||||
Filter string
|
|
||||||
Page int
|
|
||||||
PerPage int
|
|
||||||
Sort string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPageAttachments() *PageAttachments {
|
|
||||||
return &PageAttachments{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pReq *PageAttachments) Fill(r *http.Request) (err error) {
|
|
||||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
|
||||||
err = json.NewDecoder(r.Body).Decode(pReq)
|
|
||||||
|
|
||||||
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["filter"]; ok {
|
|
||||||
|
|
||||||
pReq.Filter = val
|
|
||||||
}
|
|
||||||
if val, ok := get["page"]; ok {
|
|
||||||
|
|
||||||
pReq.Page = parseInt(val)
|
|
||||||
}
|
|
||||||
if val, ok := get["perPage"]; ok {
|
|
||||||
|
|
||||||
pReq.PerPage = parseInt(val)
|
|
||||||
}
|
|
||||||
if val, ok := get["sort"]; ok {
|
|
||||||
|
|
||||||
pReq.Sort = val
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RequestFiller = NewPageAttachments()
|
|
||||||
|
|
||||||
// Page tree request parameters
|
// Page tree request parameters
|
||||||
type PageTree struct {
|
type PageTree struct {
|
||||||
}
|
}
|
||||||
@@ -455,3 +394,50 @@ func (pReq *PageDelete) Fill(r *http.Request) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ RequestFiller = NewPageDelete()
|
var _ RequestFiller = NewPageDelete()
|
||||||
|
|
||||||
|
// Page upload request parameters
|
||||||
|
type PageUpload struct {
|
||||||
|
PageID uint64 `json:",string"`
|
||||||
|
Upload *multipart.FileHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPageUpload() *PageUpload {
|
||||||
|
return &PageUpload{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pReq *PageUpload) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(pReq)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
err = nil
|
||||||
|
case err != nil:
|
||||||
|
return errors.Wrap(err, "error parsing http request body")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.ParseMultipartForm(32 << 20); 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])
|
||||||
|
}
|
||||||
|
|
||||||
|
pReq.PageID = parseUInt64(chi.URLParam(r, "pageID"))
|
||||||
|
if _, pReq.Upload, err = r.FormFile("upload"); err != nil {
|
||||||
|
return errors.Wrap(err, "error procesing uploaded file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewPageUpload()
|
||||||
|
|||||||
384
crm/rest/request/record.go
Normal file
384
crm/rest/request/record.go
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
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 `record.go`, `record.util.go` or `record_test.go` to
|
||||||
|
implement your API calls, helper functions and tests. The file `record.go`
|
||||||
|
is only generated the first time, and will not be overwritten if it exists.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = chi.URLParam
|
||||||
|
var _ = multipart.FileHeader{}
|
||||||
|
|
||||||
|
// Record report request parameters
|
||||||
|
type RecordReport struct {
|
||||||
|
Metrics string
|
||||||
|
Dimensions string
|
||||||
|
Filter string
|
||||||
|
ModuleID uint64 `json:",string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordReport() *RecordReport {
|
||||||
|
return &RecordReport{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rReq *RecordReport) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(rReq)
|
||||||
|
|
||||||
|
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["metrics"]; ok {
|
||||||
|
|
||||||
|
rReq.Metrics = val
|
||||||
|
}
|
||||||
|
if val, ok := get["dimensions"]; ok {
|
||||||
|
|
||||||
|
rReq.Dimensions = val
|
||||||
|
}
|
||||||
|
if val, ok := get["filter"]; ok {
|
||||||
|
|
||||||
|
rReq.Filter = val
|
||||||
|
}
|
||||||
|
rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewRecordReport()
|
||||||
|
|
||||||
|
// Record list request parameters
|
||||||
|
type RecordList struct {
|
||||||
|
Filter string
|
||||||
|
Page int
|
||||||
|
PerPage int
|
||||||
|
Sort string
|
||||||
|
ModuleID uint64 `json:",string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordList() *RecordList {
|
||||||
|
return &RecordList{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rReq *RecordList) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(rReq)
|
||||||
|
|
||||||
|
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["filter"]; ok {
|
||||||
|
|
||||||
|
rReq.Filter = val
|
||||||
|
}
|
||||||
|
if val, ok := get["page"]; ok {
|
||||||
|
|
||||||
|
rReq.Page = parseInt(val)
|
||||||
|
}
|
||||||
|
if val, ok := get["perPage"]; ok {
|
||||||
|
|
||||||
|
rReq.PerPage = parseInt(val)
|
||||||
|
}
|
||||||
|
if val, ok := get["sort"]; ok {
|
||||||
|
|
||||||
|
rReq.Sort = val
|
||||||
|
}
|
||||||
|
rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewRecordList()
|
||||||
|
|
||||||
|
// Record create request parameters
|
||||||
|
type RecordCreate struct {
|
||||||
|
Values types.RecordValueSet
|
||||||
|
ModuleID uint64 `json:",string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordCreate() *RecordCreate {
|
||||||
|
return &RecordCreate{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rReq *RecordCreate) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(rReq)
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewRecordCreate()
|
||||||
|
|
||||||
|
// Record read request parameters
|
||||||
|
type RecordRead struct {
|
||||||
|
RecordID uint64 `json:",string"`
|
||||||
|
ModuleID uint64 `json:",string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordRead() *RecordRead {
|
||||||
|
return &RecordRead{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rReq *RecordRead) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(rReq)
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
rReq.RecordID = parseUInt64(chi.URLParam(r, "recordID"))
|
||||||
|
rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewRecordRead()
|
||||||
|
|
||||||
|
// Record update request parameters
|
||||||
|
type RecordUpdate struct {
|
||||||
|
RecordID uint64 `json:",string"`
|
||||||
|
ModuleID uint64 `json:",string"`
|
||||||
|
Values types.RecordValueSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordUpdate() *RecordUpdate {
|
||||||
|
return &RecordUpdate{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rReq *RecordUpdate) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(rReq)
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
rReq.RecordID = parseUInt64(chi.URLParam(r, "recordID"))
|
||||||
|
rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewRecordUpdate()
|
||||||
|
|
||||||
|
// Record delete request parameters
|
||||||
|
type RecordDelete struct {
|
||||||
|
RecordID uint64 `json:",string"`
|
||||||
|
ModuleID uint64 `json:",string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordDelete() *RecordDelete {
|
||||||
|
return &RecordDelete{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rReq *RecordDelete) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(rReq)
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
rReq.RecordID = parseUInt64(chi.URLParam(r, "recordID"))
|
||||||
|
rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewRecordDelete()
|
||||||
|
|
||||||
|
// Record upload request parameters
|
||||||
|
type RecordUpload struct {
|
||||||
|
RecordID uint64 `json:",string"`
|
||||||
|
FieldName string
|
||||||
|
ModuleID uint64 `json:",string"`
|
||||||
|
Upload *multipart.FileHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordUpload() *RecordUpload {
|
||||||
|
return &RecordUpload{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rReq *RecordUpload) Fill(r *http.Request) (err error) {
|
||||||
|
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||||
|
err = json.NewDecoder(r.Body).Decode(rReq)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == io.EOF:
|
||||||
|
err = nil
|
||||||
|
case err != nil:
|
||||||
|
return errors.Wrap(err, "error parsing http request body")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = r.ParseMultipartForm(32 << 20); 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])
|
||||||
|
}
|
||||||
|
|
||||||
|
rReq.RecordID = parseUInt64(chi.URLParam(r, "recordID"))
|
||||||
|
rReq.FieldName = chi.URLParam(r, "fieldName")
|
||||||
|
rReq.ModuleID = parseUInt64(chi.URLParam(r, "moduleID"))
|
||||||
|
if _, rReq.Upload, err = r.FormFile("upload"); err != nil {
|
||||||
|
return errors.Wrap(err, "error procesing uploaded file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RequestFiller = NewRecordUpload()
|
||||||
@@ -26,6 +26,15 @@ func parseInt(s string) int {
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseInt parses a string to int
|
||||||
|
func parseUint(s string) uint {
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i, _ := strconv.ParseUint(s, 10, 32)
|
||||||
|
return uint(i)
|
||||||
|
}
|
||||||
|
|
||||||
// parseInt64 parses a string to int64
|
// parseInt64 parses a string to int64
|
||||||
func parseInt64(s string) int64 {
|
func parseInt64(s string) int64 {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
|||||||
@@ -10,10 +10,14 @@ import (
|
|||||||
func MountRoutes() func(chi.Router) {
|
func MountRoutes() func(chi.Router) {
|
||||||
var (
|
var (
|
||||||
module = Module{}.New()
|
module = Module{}.New()
|
||||||
|
record = Record{}.New()
|
||||||
page = Page{}.New()
|
page = Page{}.New()
|
||||||
chart = Chart{}.New()
|
chart = Chart{}.New()
|
||||||
trigger = Trigger{}.New()
|
trigger = Trigger{}.New()
|
||||||
notification = Notification{}.New()
|
notification = Notification{}.New()
|
||||||
|
attachment = Attachment{}.New()
|
||||||
|
// pageAttachment = PageAttachment{}.New()
|
||||||
|
// recordAttachment = RecordAttachment{}.New()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize handlers & controllers.
|
// Initialize handlers & controllers.
|
||||||
@@ -25,9 +29,13 @@ func MountRoutes() func(chi.Router) {
|
|||||||
|
|
||||||
handlers.NewPage(page).MountRoutes(r)
|
handlers.NewPage(page).MountRoutes(r)
|
||||||
handlers.NewModule(module).MountRoutes(r)
|
handlers.NewModule(module).MountRoutes(r)
|
||||||
|
handlers.NewRecord(record).MountRoutes(r)
|
||||||
handlers.NewChart(chart).MountRoutes(r)
|
handlers.NewChart(chart).MountRoutes(r)
|
||||||
handlers.NewTrigger(trigger).MountRoutes(r)
|
handlers.NewTrigger(trigger).MountRoutes(r)
|
||||||
handlers.NewNotification(notification).MountRoutes(r)
|
handlers.NewNotification(notification).MountRoutes(r)
|
||||||
|
|
||||||
|
// Use alternative handlers that support file serving
|
||||||
|
handlers.NewAttachmentDownloadable(attachment).MountRoutes(r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
287
crm/service/attachment.go
Normal file
287
crm/service/attachment.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"image"
|
||||||
|
"image/gif"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"github.com/edwvee/exiffix"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/titpetric/factory"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/crm/repository"
|
||||||
|
"github.com/crusttech/crust/crm/types"
|
||||||
|
"github.com/crusttech/crust/internal/auth"
|
||||||
|
"github.com/crusttech/crust/internal/store"
|
||||||
|
systemService "github.com/crusttech/crust/system/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
attachmentPreviewMaxWidth = 320
|
||||||
|
attachmentPreviewMaxHeight = 180
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
attachment struct {
|
||||||
|
db *factory.DB
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
store store.Store
|
||||||
|
usr systemService.UserService
|
||||||
|
|
||||||
|
attachment repository.AttachmentRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentService interface {
|
||||||
|
With(ctx context.Context) AttachmentService
|
||||||
|
|
||||||
|
FindByID(id uint64) (*types.Attachment, error)
|
||||||
|
Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error)
|
||||||
|
CreatePageAttachment(name string, size int64, fh io.ReadSeeker, pageID uint64) (*types.Attachment, error)
|
||||||
|
CreateRecordAttachment(name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (*types.Attachment, error)
|
||||||
|
OpenOriginal(att *types.Attachment) (io.ReadSeeker, error)
|
||||||
|
OpenPreview(att *types.Attachment) (io.ReadSeeker, error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func Attachment(store store.Store) AttachmentService {
|
||||||
|
return (&attachment{
|
||||||
|
store: store,
|
||||||
|
usr: systemService.DefaultUser,
|
||||||
|
}).With(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *attachment) With(ctx context.Context) AttachmentService {
|
||||||
|
db := repository.DB(ctx)
|
||||||
|
return &attachment{
|
||||||
|
db: db,
|
||||||
|
ctx: ctx,
|
||||||
|
|
||||||
|
store: svc.store,
|
||||||
|
usr: svc.usr.With(ctx),
|
||||||
|
|
||||||
|
attachment: repository.Attachment(ctx, db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *attachment) FindByID(id uint64) (*types.Attachment, error) {
|
||||||
|
// @todo [SECURITY] check if record/page can be accessed
|
||||||
|
return svc.attachment.FindByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *attachment) Find(filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error) {
|
||||||
|
// @todo [SECURITY] enforce filter combination (page / module+record+field) & check access
|
||||||
|
return svc.attachment.Find(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *attachment) OpenOriginal(att *types.Attachment) (io.ReadSeeker, error) {
|
||||||
|
if len(att.Url) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc.store.Open(att.Url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *attachment) OpenPreview(att *types.Attachment) (io.ReadSeeker, error) {
|
||||||
|
if len(att.PreviewUrl) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc.store.Open(att.PreviewUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *attachment) CreatePageAttachment(name string, size int64, fh io.ReadSeeker, pageID uint64) (*types.Attachment, error) {
|
||||||
|
var currentUserID uint64 = auth.GetIdentityFromContext(svc.ctx).Identity()
|
||||||
|
|
||||||
|
// @todo verify if current user can access this page
|
||||||
|
// @todo verify if current user can upload to this page
|
||||||
|
|
||||||
|
att := &types.Attachment{
|
||||||
|
ID: factory.Sonyflake.NextID(),
|
||||||
|
OwnerID: currentUserID,
|
||||||
|
Name: strings.TrimSpace(name),
|
||||||
|
Kind: types.PageAttachment,
|
||||||
|
}
|
||||||
|
|
||||||
|
return att, svc.create(name, size, fh, att)
|
||||||
|
}
|
||||||
|
func (svc *attachment) CreateRecordAttachment(name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (*types.Attachment, error) {
|
||||||
|
var currentUserID uint64 = auth.GetIdentityFromContext(svc.ctx).Identity()
|
||||||
|
|
||||||
|
// @todo verify if current user can access this record
|
||||||
|
// @todo verify if current user can upload to this record
|
||||||
|
|
||||||
|
att := &types.Attachment{
|
||||||
|
ID: factory.Sonyflake.NextID(),
|
||||||
|
OwnerID: currentUserID,
|
||||||
|
Name: strings.TrimSpace(name),
|
||||||
|
Kind: types.RecordAttachment,
|
||||||
|
}
|
||||||
|
|
||||||
|
return att, svc.create(name, size, fh, att)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *attachment) create(name string, size int64, fh io.ReadSeeker, att *types.Attachment) (err error) {
|
||||||
|
if svc.store == nil {
|
||||||
|
return errors.New("Can not create attachment: store handler not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract extension but make sure path.Ext is not confused by any leading/trailing dots
|
||||||
|
att.Meta.Original.Extension = strings.Trim(path.Ext(strings.Trim(name, ".")), ".")
|
||||||
|
|
||||||
|
att.Meta.Original.Size = size
|
||||||
|
if att.Meta.Original.Mimetype, err = svc.extractMimetype(fh); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"Processing uploaded file (name: %s, size: %d, mimetype: %s)",
|
||||||
|
att.Name,
|
||||||
|
att.Meta.Original.Size,
|
||||||
|
att.Meta.Original.Mimetype)
|
||||||
|
|
||||||
|
att.Url = svc.store.Original(att.ID, att.Meta.Original.Extension)
|
||||||
|
if err = svc.store.Save(att.Url, fh); err != nil {
|
||||||
|
log.Print(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process image: extract width, height, make preview
|
||||||
|
log.Printf("Image processed, error: %v", svc.processImage(fh, att))
|
||||||
|
|
||||||
|
log.Printf("File %s stored as %s", att.Name, att.Url)
|
||||||
|
|
||||||
|
return svc.db.Transaction(func() (err error) {
|
||||||
|
if att, err = svc.attachment.Create(att); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *attachment) extractMimetype(file io.ReadSeeker) (mimetype string, err error) {
|
||||||
|
if _, err = file.Seek(0, 0); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we rewind when we're done
|
||||||
|
defer file.Seek(0, 0)
|
||||||
|
|
||||||
|
// See http.DetectContentType about 512 bytes
|
||||||
|
var buf = make([]byte, 512)
|
||||||
|
if _, err = file.Read(buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.DetectContentType(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *attachment) processImage(original io.ReadSeeker, att *types.Attachment) (err error) {
|
||||||
|
if !strings.HasPrefix(att.Meta.Original.Mimetype, "image/") {
|
||||||
|
// Only supporting previews from images (for now)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
preview image.Image
|
||||||
|
opts []imaging.EncodeOption
|
||||||
|
format imaging.Format
|
||||||
|
previewFormat imaging.Format
|
||||||
|
animated bool
|
||||||
|
f2m = map[imaging.Format]string{
|
||||||
|
imaging.JPEG: "image/jpeg",
|
||||||
|
imaging.GIF: "image/gif",
|
||||||
|
}
|
||||||
|
|
||||||
|
f2e = map[imaging.Format]string{
|
||||||
|
imaging.JPEG: "jpg",
|
||||||
|
imaging.GIF: "gif",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if _, err = original.Seek(0, 0); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if format, err = imaging.FormatFromExtension(att.Meta.Original.Extension); err != nil {
|
||||||
|
return errors.Wrapf(err, "Could not get format from extension '%s'", att.Meta.Original.Extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
previewFormat = format
|
||||||
|
|
||||||
|
if imaging.JPEG == format {
|
||||||
|
// Rotate image if needed
|
||||||
|
if preview, _, err = exiffix.Decode(original); err != nil {
|
||||||
|
//return errors.Wrapf(err, "Could not decode EXIF from JPEG")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if imaging.GIF == format {
|
||||||
|
// Decode all and check loops & delay to determine if GIF is animated or not
|
||||||
|
if cfg, err := gif.DecodeAll(original); err == nil {
|
||||||
|
animated = cfg.LoopCount > 0 || len(cfg.Delay) > 1
|
||||||
|
|
||||||
|
// Use first image for the preview
|
||||||
|
preview = cfg.Image[0]
|
||||||
|
} else {
|
||||||
|
return errors.Wrapf(err, "Could not decode gif config")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Use GIF preview for GIFs and JPEG for everything else!
|
||||||
|
previewFormat = imaging.JPEG
|
||||||
|
|
||||||
|
// Store with a bit lower quality
|
||||||
|
opts = append(opts, imaging.JPEGQuality(85))
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of JPEG we decode the image and rotate it beforehand
|
||||||
|
// other cases are handled here
|
||||||
|
if preview == nil {
|
||||||
|
if preview, err = imaging.Decode(original); err != nil {
|
||||||
|
return errors.Wrapf(err, "Could not decode original image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var width, height = preview.Bounds().Max.X, preview.Bounds().Max.Y
|
||||||
|
att.SetOriginalImageMeta(width, height, animated)
|
||||||
|
|
||||||
|
if width > attachmentPreviewMaxWidth && width > height {
|
||||||
|
// Landscape does not fit
|
||||||
|
preview = imaging.Resize(preview, attachmentPreviewMaxWidth, 0, imaging.Lanczos)
|
||||||
|
} else if height > attachmentPreviewMaxHeight {
|
||||||
|
// Height does not fit
|
||||||
|
preview = imaging.Resize(preview, 0, attachmentPreviewMaxHeight, imaging.Lanczos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get dimensions from the preview
|
||||||
|
width, height = preview.Bounds().Max.X, preview.Bounds().Max.Y
|
||||||
|
|
||||||
|
log.Printf("Generated preview %s (%dx%dpx)", previewFormat, width, height)
|
||||||
|
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
if err = imaging.Encode(buf, preview, previewFormat); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := att.SetPreviewImageMeta(width, height, false)
|
||||||
|
meta.Size = int64(buf.Len())
|
||||||
|
meta.Mimetype = f2m[previewFormat]
|
||||||
|
meta.Extension = f2e[previewFormat]
|
||||||
|
|
||||||
|
// Can and how we make a preview of this attachment?
|
||||||
|
att.PreviewUrl = svc.store.Preview(att.ID, meta.Extension)
|
||||||
|
|
||||||
|
return svc.store.Save(att.PreviewUrl, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ AttachmentService = &attachment{}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/crusttech/crust/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -19,10 +22,16 @@ var (
|
|||||||
DefaultPage PageService
|
DefaultPage PageService
|
||||||
DefaultNotification NotificationService
|
DefaultNotification NotificationService
|
||||||
DefaultPermissions PermissionsService
|
DefaultPermissions PermissionsService
|
||||||
|
DefaultAttachment AttachmentService
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
o.Do(func() {
|
o.Do(func() {
|
||||||
|
fs, err := store.New("var/store")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to initialize store: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
DefaultRecord = Record()
|
DefaultRecord = Record()
|
||||||
DefaultModule = Module()
|
DefaultModule = Module()
|
||||||
DefaultTrigger = Trigger()
|
DefaultTrigger = Trigger()
|
||||||
@@ -30,5 +39,6 @@ func Init() {
|
|||||||
DefaultChart = Chart()
|
DefaultChart = Chart()
|
||||||
DefaultNotification = Notification()
|
DefaultNotification = Notification()
|
||||||
DefaultPermissions = Permissions()
|
DefaultPermissions = Permissions()
|
||||||
|
DefaultAttachment = Attachment(fs)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
67
crm/types/attachment.gen.go
Normal file
67
crm/types/attachment.gen.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// Hello! This file is auto-generated.
|
||||||
|
|
||||||
|
type (
|
||||||
|
|
||||||
|
// AttachmentSet slice of Attachment
|
||||||
|
//
|
||||||
|
// This type is auto-generated.
|
||||||
|
AttachmentSet []*Attachment
|
||||||
|
)
|
||||||
|
|
||||||
|
// Walk iterates through every slice item and calls w(Attachment) err
|
||||||
|
//
|
||||||
|
// This function is auto-generated.
|
||||||
|
func (set AttachmentSet) Walk(w func(*Attachment) error) (err error) {
|
||||||
|
for i := range set {
|
||||||
|
if err = w(set[i]); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter iterates through every slice item, calls f(Attachment) (bool, err) and return filtered slice
|
||||||
|
//
|
||||||
|
// This function is auto-generated.
|
||||||
|
func (set AttachmentSet) Filter(f func(*Attachment) (bool, error)) (out AttachmentSet, err error) {
|
||||||
|
var ok bool
|
||||||
|
out = AttachmentSet{}
|
||||||
|
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 AttachmentSet) FindByID(ID uint64) *Attachment {
|
||||||
|
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 AttachmentSet) IDs() (IDs []uint64) {
|
||||||
|
IDs = make([]uint64, len(set))
|
||||||
|
|
||||||
|
for i := range set {
|
||||||
|
IDs[i] = set[i].ID
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
104
crm/types/attachment.go
Normal file
104
crm/types/attachment.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Attachment struct {
|
||||||
|
ID uint64 `db:"id" json:"attachmentID,omitempty"`
|
||||||
|
OwnerID uint64 `db:"rel_owner" json:"ownerID,omitempty"`
|
||||||
|
Kind string `db:"kind" json:"-"`
|
||||||
|
Url string `db:"url" json:"url,omitempty"`
|
||||||
|
PreviewUrl string `db:"preview_url"json:"previewUrl,omitempty"`
|
||||||
|
Name string `db:"name" json:"name,omitempty"`
|
||||||
|
Meta attachmentMeta `db:"meta" json:"meta"`
|
||||||
|
CreatedAt time.Time `db:"created_at" json:"createdAt,omitempty"`
|
||||||
|
UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"`
|
||||||
|
DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachmentFilter is used for filtering and as a return value from Find
|
||||||
|
AttachmentFilter struct {
|
||||||
|
Kind string `json:"kind,omitempty"`
|
||||||
|
PageID uint64 `json:"pageID,string,omitempty"`
|
||||||
|
RecordID uint64 `json:"recordID,string,omitempty"`
|
||||||
|
ModuleID uint64 `json:"moduleID,string,omitempty"`
|
||||||
|
FieldName string `json:"fieldName,omitempty"`
|
||||||
|
Filter string `json:"filter"`
|
||||||
|
Page uint `json:"page"`
|
||||||
|
PerPage uint `json:"perPage"`
|
||||||
|
Sort string `json:"sort"`
|
||||||
|
Count uint `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
attachmentImageMeta struct {
|
||||||
|
Width int `json:"width,omitempty"`
|
||||||
|
Height int `json:"height,omitempty"`
|
||||||
|
Animated bool `json:"animated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
attachmentFileMeta struct {
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Extension string `json:"ext"`
|
||||||
|
Mimetype string `json:"mimetype"`
|
||||||
|
Image *attachmentImageMeta `json:"image,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
attachmentMeta struct {
|
||||||
|
Original attachmentFileMeta `json:"original"`
|
||||||
|
Preview *attachmentFileMeta `json:"preview,omitempty"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PageAttachment string = "page"
|
||||||
|
RecordAttachment string = "record"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Attachment) SetOriginalImageMeta(width, height int, animated bool) *attachmentFileMeta {
|
||||||
|
a.imageMeta(&a.Meta.Original, width, height, animated)
|
||||||
|
return &a.Meta.Original
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attachment) SetPreviewImageMeta(width, height int, animated bool) *attachmentFileMeta {
|
||||||
|
if a.Meta.Preview == nil {
|
||||||
|
a.Meta.Preview = &attachmentFileMeta{}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.imageMeta(a.Meta.Preview, width, height, animated)
|
||||||
|
return a.Meta.Preview
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Attachment) imageMeta(in *attachmentFileMeta, width, height int, animated bool) {
|
||||||
|
if in.Image == nil {
|
||||||
|
in.Image = &attachmentImageMeta{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if width > 0 && height > 0 {
|
||||||
|
in.Image.Animated = animated
|
||||||
|
in.Image.Width = width
|
||||||
|
in.Image.Height = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (meta *attachmentMeta) Scan(value interface{}) error {
|
||||||
|
switch value.(type) {
|
||||||
|
case nil:
|
||||||
|
*meta = attachmentMeta{}
|
||||||
|
case []uint8:
|
||||||
|
if err := json.Unmarshal(value.([]byte), meta); err != nil {
|
||||||
|
return errors.Wrapf(err, "Can not scan '%v' into attachmentMeta", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (meta attachmentMeta) Value() (driver.Value, error) {
|
||||||
|
return json.Marshal(meta)
|
||||||
|
}
|
||||||
@@ -150,7 +150,7 @@ func (set ModuleFieldSet) FilterByModule(moduleID uint64) (ff ModuleFieldSet) {
|
|||||||
|
|
||||||
// IsRef tells us if value of this field be a reference to something (another record, user)?
|
// IsRef tells us if value of this field be a reference to something (another record, user)?
|
||||||
func (f ModuleField) IsRef() bool {
|
func (f ModuleField) IsRef() bool {
|
||||||
return f.Kind == "Record" || f.Kind == "Owner"
|
return f.Kind == "Record" || f.Kind == "Owner" || f.Kind == "File"
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserIDs returns a slice of user IDs from all items in the set
|
// UserIDs returns a slice of user IDs from all items in the set
|
||||||
|
|||||||
@@ -1,3 +1,76 @@
|
|||||||
|
# Attachments
|
||||||
|
|
||||||
|
## 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 |
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
## 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 |
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Charts
|
# Charts
|
||||||
|
|
||||||
## List/read charts from module section
|
## List/read charts from module section
|
||||||
@@ -154,102 +227,6 @@ CRM module definitions
|
|||||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||||
| moduleID | uint64 | PATH | Module ID | N/A | YES |
|
| moduleID | uint64 | PATH | Module ID | N/A | YES |
|
||||||
|
|
||||||
## Generates report from module records
|
|
||||||
|
|
||||||
#### Method
|
|
||||||
|
|
||||||
| URI | Protocol | Method | Authentication |
|
|
||||||
| --- | -------- | ------ | -------------- |
|
|
||||||
| `/module/{moduleID}/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? |
|
|
||||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
|
||||||
| moduleID | uint64 | PATH | Module ID | N/A | YES |
|
|
||||||
| values | types.RecordValueSet | POST | Record values | 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? |
|
|
||||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
|
||||||
| moduleID | uint64 | PATH | Module ID | N/A | YES |
|
|
||||||
| recordID | uint64 | PATH | Record 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? |
|
|
||||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
|
||||||
| moduleID | uint64 | PATH | Module ID | N/A | YES |
|
|
||||||
| recordID | uint64 | PATH | Record 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? |
|
|
||||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
|
||||||
| moduleID | uint64 | PATH | Module ID | N/A | YES |
|
|
||||||
| recordID | uint64 | PATH | Record ID | N/A | YES |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -391,6 +368,141 @@ CRM module pages
|
|||||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||||
| pageID | uint64 | PATH | Page ID | N/A | YES |
|
| 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 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Records
|
||||||
|
|
||||||
|
CRM records
|
||||||
|
|
||||||
|
## Generates report from module records
|
||||||
|
|
||||||
|
#### Method
|
||||||
|
|
||||||
|
| URI | Protocol | Method | Authentication |
|
||||||
|
| --- | -------- | ------ | -------------- |
|
||||||
|
| `/module/{moduleID}/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 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func Init() {
|
|||||||
o.Do(func() {
|
o.Do(func() {
|
||||||
fs, err := store.New("var/store")
|
fs, err := store.New("var/store")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize stor: %v", err)
|
log.Fatalf("Failed to initialize store: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultPermissions = Permissions()
|
DefaultPermissions = Permissions()
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user