@quatrain/api-server-express 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/ExpressAdapter.d.ts +2 -1
- package/dist/adapters/ExpressAdapter.js +43 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +1 -7
- package/package.json +5 -6
- package/src/adapters/ExpressAdapter.ts +46 -2
- package/src/index.ts +0 -4
- package/README.md +0 -13
- package/dist/endpoints/CrudEndpoint.d.ts +0 -3
- package/dist/endpoints/CrudEndpoint.js +0 -101
- package/dist/endpoints/ListEndpoint.d.ts +0 -3
- package/dist/endpoints/ListEndpoint.js +0 -27
- package/dist/endpoints/ValuesEndpoint.d.ts +0 -3
- package/dist/endpoints/ValuesEndpoint.js +0 -59
- package/src/endpoints/CrudEndpoint.ts +0 -106
- package/src/endpoints/ListEndpoint.ts +0 -30
- package/src/endpoints/ValuesEndpoint.ts +0 -66
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import { ServerAdapter, ApiHandler, EndpointHandler, EndpointOptions } from '@quatrain/api';
|
|
2
|
+
import { ServerAdapter, ApiHandler, EndpointHandler, EndpointOptions, ApiMiddleware } from '@quatrain/api';
|
|
3
3
|
export declare class ExpressAdapter implements ServerAdapter {
|
|
4
4
|
private appOrRouter;
|
|
5
5
|
private config;
|
|
@@ -12,6 +12,7 @@ export declare class ExpressAdapter implements ServerAdapter {
|
|
|
12
12
|
put(path: string, handler: ApiHandler): void;
|
|
13
13
|
delete(path: string, handler: ApiHandler): void;
|
|
14
14
|
use(middleware: any): void;
|
|
15
|
+
addMiddleware(middleware: ApiMiddleware): void;
|
|
15
16
|
createRouter(path: string): ServerAdapter;
|
|
16
17
|
start(port: number, callback?: () => void): void;
|
|
17
18
|
getNativeInstance(): any;
|
|
@@ -32,7 +32,8 @@ class ExpressAdapter {
|
|
|
32
32
|
const apiReq = {
|
|
33
33
|
body: req.body,
|
|
34
34
|
params: req.params,
|
|
35
|
-
query: req.query
|
|
35
|
+
query: req.query,
|
|
36
|
+
headers: req.headers
|
|
36
37
|
};
|
|
37
38
|
const apiRes = {
|
|
38
39
|
status: (code) => {
|
|
@@ -77,6 +78,47 @@ class ExpressAdapter {
|
|
|
77
78
|
use(middleware) {
|
|
78
79
|
this.appOrRouter.use(middleware);
|
|
79
80
|
}
|
|
81
|
+
addMiddleware(middleware) {
|
|
82
|
+
const expressMiddleware = async (req, res, next) => {
|
|
83
|
+
const apiReq = {
|
|
84
|
+
body: req.body,
|
|
85
|
+
params: req.params,
|
|
86
|
+
query: req.query,
|
|
87
|
+
headers: req.headers
|
|
88
|
+
};
|
|
89
|
+
const apiRes = {
|
|
90
|
+
status: (code) => {
|
|
91
|
+
res.status(code);
|
|
92
|
+
return apiRes;
|
|
93
|
+
},
|
|
94
|
+
json: (data) => {
|
|
95
|
+
res.json(data);
|
|
96
|
+
},
|
|
97
|
+
send: (data) => {
|
|
98
|
+
res.send(data);
|
|
99
|
+
},
|
|
100
|
+
setHeader: (name, value) => {
|
|
101
|
+
res.setHeader(name, value);
|
|
102
|
+
},
|
|
103
|
+
write: (data) => {
|
|
104
|
+
res.write(data);
|
|
105
|
+
},
|
|
106
|
+
end: () => {
|
|
107
|
+
res.end();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
const shouldContinue = await middleware(apiReq, apiRes);
|
|
112
|
+
if (shouldContinue) {
|
|
113
|
+
next();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
next(err);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
this.appOrRouter.use(expressMiddleware);
|
|
121
|
+
}
|
|
80
122
|
createRouter(path) {
|
|
81
123
|
const router = express_1.default.Router();
|
|
82
124
|
this.appOrRouter.use(path, router);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.ExpressAdapter = void 0;
|
|
4
4
|
var ExpressAdapter_1 = require("./adapters/ExpressAdapter");
|
|
5
5
|
Object.defineProperty(exports, "ExpressAdapter", { enumerable: true, get: function () { return ExpressAdapter_1.ExpressAdapter; } });
|
|
6
|
-
var CrudEndpoint_1 = require("./endpoints/CrudEndpoint");
|
|
7
|
-
Object.defineProperty(exports, "CrudEndpoint", { enumerable: true, get: function () { return CrudEndpoint_1.CrudEndpoint; } });
|
|
8
|
-
var ListEndpoint_1 = require("./endpoints/ListEndpoint");
|
|
9
|
-
Object.defineProperty(exports, "ListEndpoint", { enumerable: true, get: function () { return ListEndpoint_1.ListEndpoint; } });
|
|
10
|
-
var ValuesEndpoint_1 = require("./endpoints/ValuesEndpoint");
|
|
11
|
-
Object.defineProperty(exports, "ValuesEndpoint", { enumerable: true, get: function () { return ValuesEndpoint_1.ValuesEndpoint; } });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quatrain/api-server-express",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.8",
|
|
4
|
+
"description": "Express Adapter for Quatrain API Server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"bun": "src/index.ts",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
17
|
"url": "git+https://github.com/Quatrain/Core.git",
|
|
18
|
-
"directory": "packages/api-server"
|
|
18
|
+
"directory": "packages/api-server-express"
|
|
19
19
|
},
|
|
20
20
|
"author": "Quatrain Développement SAS <developers@quatrain.com>",
|
|
21
21
|
"license": "AGPL-3.0-only",
|
|
@@ -29,9 +29,8 @@
|
|
|
29
29
|
"typescript": "^5.2.2"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@quatrain/api": "^1.1.
|
|
33
|
-
"@quatrain/
|
|
34
|
-
"@quatrain/core": "^1.2.5",
|
|
32
|
+
"@quatrain/api": "^1.1.4",
|
|
33
|
+
"@quatrain/api-server": "^1.1.8",
|
|
35
34
|
"express": "^4.19.2"
|
|
36
35
|
},
|
|
37
36
|
"scripts": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import express from 'express'
|
|
2
|
-
import { ServerAdapter, ApiHandler, ApiRequest, ApiResponse, EndpointHandler, EndpointOptions } from '@quatrain/api'
|
|
2
|
+
import { ServerAdapter, ApiHandler, ApiRequest, ApiResponse, EndpointHandler, EndpointOptions, ApiMiddleware } from '@quatrain/api'
|
|
3
3
|
|
|
4
4
|
export class ExpressAdapter implements ServerAdapter {
|
|
5
5
|
constructor(
|
|
@@ -28,7 +28,8 @@ export class ExpressAdapter implements ServerAdapter {
|
|
|
28
28
|
const apiReq: ApiRequest = {
|
|
29
29
|
body: req.body,
|
|
30
30
|
params: req.params,
|
|
31
|
-
query: req.query
|
|
31
|
+
query: req.query,
|
|
32
|
+
headers: req.headers as Record<string, string | string[] | undefined>
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const apiRes: ApiResponse = {
|
|
@@ -80,6 +81,49 @@ export class ExpressAdapter implements ServerAdapter {
|
|
|
80
81
|
(this.appOrRouter as express.Router).use(middleware)
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
addMiddleware(middleware: ApiMiddleware): void {
|
|
85
|
+
const expressMiddleware = async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
86
|
+
const apiReq: ApiRequest = {
|
|
87
|
+
body: req.body,
|
|
88
|
+
params: req.params,
|
|
89
|
+
query: req.query,
|
|
90
|
+
headers: req.headers as Record<string, string | string[] | undefined>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const apiRes: ApiResponse = {
|
|
94
|
+
status: (code: number) => {
|
|
95
|
+
res.status(code)
|
|
96
|
+
return apiRes
|
|
97
|
+
},
|
|
98
|
+
json: (data: any) => {
|
|
99
|
+
res.json(data)
|
|
100
|
+
},
|
|
101
|
+
send: (data: string) => {
|
|
102
|
+
res.send(data)
|
|
103
|
+
},
|
|
104
|
+
setHeader: (name: string, value: string) => {
|
|
105
|
+
res.setHeader(name, value)
|
|
106
|
+
},
|
|
107
|
+
write: (data: string) => {
|
|
108
|
+
res.write(data)
|
|
109
|
+
},
|
|
110
|
+
end: () => {
|
|
111
|
+
res.end()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const shouldContinue = await middleware(apiReq, apiRes)
|
|
117
|
+
if (shouldContinue) {
|
|
118
|
+
next()
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
next(err)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
(this.appOrRouter as express.Router).use(expressMiddleware)
|
|
125
|
+
}
|
|
126
|
+
|
|
83
127
|
createRouter(path: string): ServerAdapter {
|
|
84
128
|
const router = express.Router()
|
|
85
129
|
;(this.appOrRouter as express.Router).use(path, router)
|
package/src/index.ts
CHANGED
package/README.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# @quatrain/api-server-express
|
|
2
|
-
|
|
3
|
-
The `@quatrain/api-server-express` package provides the backend REST layer for Quatrain, exposing the underlying DataObjects and Backend engines through HTTP endpoints.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
- **Express Adapter**: A lightweight wrapper around Express to bootstrap APIs instantly.
|
|
7
|
-
- **Auto-generated Endpoints**: Pre-built `CrudEndpoint` and `ListEndpoint` handlers to instantly expose a Quatrain model (e.g. `StudioProperty`, `StudioModel`) as a complete REST API.
|
|
8
|
-
- **Middleware Support**: Pluggable Express middlewares.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
## Getting Started
|
|
12
|
-
|
|
13
|
-
Refer to the `HOWTO.md` file for usage examples.
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CrudEndpoint = void 0;
|
|
4
|
-
const backend_1 = require("@quatrain/backend");
|
|
5
|
-
const CrudEndpoint = (ModelClass) => {
|
|
6
|
-
return (router, path, options) => {
|
|
7
|
-
const methods = options.methods || ['CREATE', 'READ', 'UPDATE', 'DELETE'];
|
|
8
|
-
// C: Create
|
|
9
|
-
if (methods.includes('CREATE')) {
|
|
10
|
-
router.post(path, async (req, res) => {
|
|
11
|
-
try {
|
|
12
|
-
const data = req.body;
|
|
13
|
-
const newObj = await ModelClass.factory();
|
|
14
|
-
Object.keys(data).forEach((key) => {
|
|
15
|
-
if (newObj.has(key)) {
|
|
16
|
-
newObj.set(key, data[key]);
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
newObj.validate();
|
|
20
|
-
await newObj.save();
|
|
21
|
-
res.status(201).json(newObj.dataObject.toJSON());
|
|
22
|
-
}
|
|
23
|
-
catch (e) {
|
|
24
|
-
if (e.name === 'ValidationError') {
|
|
25
|
-
res.status(400).json({ error: e.message, validationErrors: e.errors });
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
backend_1.Backend.error(`[API Error] POST: ${e.message}`);
|
|
29
|
-
res.status(400).json({ error: e.message });
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
// R: Read (Single by ID)
|
|
35
|
-
if (methods.includes('READ')) {
|
|
36
|
-
const getPath = path.endsWith('/') ? `${path}:id` : `${path}/:id`;
|
|
37
|
-
router.get(getPath, async (req, res) => {
|
|
38
|
-
try {
|
|
39
|
-
const obj = await ModelClass.fromBackend(req.params.id);
|
|
40
|
-
if (!obj) {
|
|
41
|
-
return res.status(404).json({ error: 'Object not found' });
|
|
42
|
-
}
|
|
43
|
-
res.json(obj.dataObject.toJSON());
|
|
44
|
-
}
|
|
45
|
-
catch (e) {
|
|
46
|
-
backend_1.Backend.error(`[API Error] GET /${req.params.id}: ${e.message}`);
|
|
47
|
-
res.status(500).json({ error: e.message });
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
// U: Update
|
|
52
|
-
if (methods.includes('UPDATE')) {
|
|
53
|
-
const putPath = path.endsWith('/') ? `${path}:id` : `${path}/:id`;
|
|
54
|
-
router.put(putPath, async (req, res) => {
|
|
55
|
-
try {
|
|
56
|
-
const data = req.body;
|
|
57
|
-
const obj = await ModelClass.fromBackend(req.params.id);
|
|
58
|
-
if (!obj) {
|
|
59
|
-
return res.status(404).json({ error: 'Object not found' });
|
|
60
|
-
}
|
|
61
|
-
Object.keys(data).forEach((key) => {
|
|
62
|
-
if (obj.has(key)) {
|
|
63
|
-
obj.set(key, data[key]);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
obj.validate();
|
|
67
|
-
await obj.save();
|
|
68
|
-
res.json(obj.dataObject.toJSON());
|
|
69
|
-
}
|
|
70
|
-
catch (e) {
|
|
71
|
-
if (e.name === 'ValidationError') {
|
|
72
|
-
res.status(400).json({ error: e.message, validationErrors: e.errors });
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
backend_1.Backend.error(`[API Error] PUT /${req.params.id}: ${e.message}`);
|
|
76
|
-
res.status(400).json({ error: e.message });
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
// D: Delete
|
|
82
|
-
if (methods.includes('DELETE')) {
|
|
83
|
-
const deletePath = path.endsWith('/') ? `${path}:id` : `${path}/:id`;
|
|
84
|
-
router.delete(deletePath, async (req, res) => {
|
|
85
|
-
try {
|
|
86
|
-
const obj = await ModelClass.fromBackend(req.params.id);
|
|
87
|
-
if (!obj) {
|
|
88
|
-
return res.status(404).json({ error: 'Object not found' });
|
|
89
|
-
}
|
|
90
|
-
await obj.delete();
|
|
91
|
-
res.json({ success: true });
|
|
92
|
-
}
|
|
93
|
-
catch (e) {
|
|
94
|
-
backend_1.Backend.error(`[API Error] DELETE /${req.params.id}: ${e.message}`);
|
|
95
|
-
res.status(500).json({ error: e.message });
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
};
|
|
101
|
-
exports.CrudEndpoint = CrudEndpoint;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ListEndpoint = void 0;
|
|
4
|
-
const backend_1 = require("@quatrain/backend");
|
|
5
|
-
const ListEndpoint = (ModelClass) => {
|
|
6
|
-
return (router, path, options) => {
|
|
7
|
-
router.get(path, async (req, res) => {
|
|
8
|
-
try {
|
|
9
|
-
let q = ModelClass.query();
|
|
10
|
-
// Apply all query parameters as exact match filters
|
|
11
|
-
for (const [key, value] of Object.entries(req.query)) {
|
|
12
|
-
q = q.where(key, value, 'equals');
|
|
13
|
-
}
|
|
14
|
-
const results = await q.execute('dataObjects');
|
|
15
|
-
res.json({
|
|
16
|
-
items: results.items.map((r) => r.toJSON()),
|
|
17
|
-
meta: results.meta
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
catch (e) {
|
|
21
|
-
backend_1.Backend.error(`[API Error] GET (List): ${e.message}`);
|
|
22
|
-
res.status(500).json({ error: e.message });
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
exports.ListEndpoint = ListEndpoint;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ValuesEndpoint = void 0;
|
|
4
|
-
const backend_1 = require("@quatrain/backend");
|
|
5
|
-
const ValuesEndpoint = (ModelClass) => {
|
|
6
|
-
return (router, path, options) => {
|
|
7
|
-
// Create the endpoint route, assuming 'path' is something like '/api/users'
|
|
8
|
-
// We'll map it to '/api/users/values'
|
|
9
|
-
const valuesPath = path.endsWith('/') ? `${path}values` : `${path}/values`;
|
|
10
|
-
router.get(valuesPath, async (req, res) => {
|
|
11
|
-
try {
|
|
12
|
-
const results = await ModelClass.query()
|
|
13
|
-
.where('status', 'deleted', 'notEquals')
|
|
14
|
-
.execute('dataObjects');
|
|
15
|
-
// Identify exposed fields from PROPS_DEFINITION
|
|
16
|
-
const propsDef = ModelClass.PROPS_DEFINITION || [];
|
|
17
|
-
const exposedFields = propsDef
|
|
18
|
-
.filter((p) => p.htmlType && p.htmlType !== 'off')
|
|
19
|
-
.map((p) => p.name);
|
|
20
|
-
const searchFields = ['name', ...propsDef
|
|
21
|
-
.filter((p) => p.type === 'StringProperty' && p.fullSearch === true)
|
|
22
|
-
.map((p) => p.name)];
|
|
23
|
-
let values = results.items.map((dataObject) => {
|
|
24
|
-
const payload = {
|
|
25
|
-
value: dataObject.uid,
|
|
26
|
-
name: dataObject.has('name') ? dataObject.get('name') : dataObject.uid
|
|
27
|
-
};
|
|
28
|
-
for (const field of exposedFields) {
|
|
29
|
-
if (dataObject.has(field)) {
|
|
30
|
-
payload[field] = dataObject.get(field);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
const searchVals = searchFields.map(f => {
|
|
34
|
-
const val = dataObject.get(f);
|
|
35
|
-
return typeof val === 'string' ? val : '';
|
|
36
|
-
}).filter(Boolean);
|
|
37
|
-
payload._search = searchVals.join(' ').toLowerCase();
|
|
38
|
-
return payload;
|
|
39
|
-
});
|
|
40
|
-
const qParam = req.query.q;
|
|
41
|
-
if (qParam) {
|
|
42
|
-
const searchStr = qParam.toLowerCase();
|
|
43
|
-
values = values.filter((payload) => {
|
|
44
|
-
return searchFields.some(field => {
|
|
45
|
-
const val = payload[field];
|
|
46
|
-
return typeof val === 'string' && val.toLowerCase().includes(searchStr);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
res.json(values);
|
|
51
|
-
}
|
|
52
|
-
catch (e) {
|
|
53
|
-
backend_1.Backend.error(`[API Error] GET ${valuesPath}: ${e.message}`);
|
|
54
|
-
res.status(500).json({ error: e.message });
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
};
|
|
58
|
-
};
|
|
59
|
-
exports.ValuesEndpoint = ValuesEndpoint;
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { EndpointHandler, ServerAdapter, EndpointOptions } from '@quatrain/api'
|
|
2
|
-
import { BaseObject, DataObjectClass } from '@quatrain/core'
|
|
3
|
-
import { Backend } from '@quatrain/backend'
|
|
4
|
-
import { ValidationError } from '@quatrain/core'
|
|
5
|
-
|
|
6
|
-
export const CrudEndpoint = (ModelClass: typeof BaseObject): EndpointHandler => {
|
|
7
|
-
return (router: ServerAdapter, path: string, options: EndpointOptions) => {
|
|
8
|
-
const methods = options.methods || ['CREATE', 'READ', 'UPDATE', 'DELETE']
|
|
9
|
-
|
|
10
|
-
// C: Create
|
|
11
|
-
if (methods.includes('CREATE')) {
|
|
12
|
-
router.post(path, async (req, res) => {
|
|
13
|
-
try {
|
|
14
|
-
const data = req.body
|
|
15
|
-
const newObj = await ModelClass.factory()
|
|
16
|
-
|
|
17
|
-
Object.keys(data).forEach((key) => {
|
|
18
|
-
if (newObj.has(key)) {
|
|
19
|
-
newObj.set(key, data[key])
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
(newObj as any).validate()
|
|
24
|
-
await (newObj as any).save()
|
|
25
|
-
res.status(201).json(newObj.dataObject.toJSON())
|
|
26
|
-
} catch (e: any) {
|
|
27
|
-
if (e.name === 'ValidationError') {
|
|
28
|
-
res.status(400).json({ error: e.message, validationErrors: e.errors })
|
|
29
|
-
} else {
|
|
30
|
-
Backend.error(`[API Error] POST: ${e.message}`)
|
|
31
|
-
res.status(400).json({ error: e.message })
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// R: Read (Single by ID)
|
|
38
|
-
if (methods.includes('READ')) {
|
|
39
|
-
const getPath = path.endsWith('/') ? `${path}:id` : `${path}/:id`
|
|
40
|
-
router.get(getPath, async (req, res) => {
|
|
41
|
-
try {
|
|
42
|
-
const obj = await (ModelClass as any).fromBackend(req.params.id)
|
|
43
|
-
if (!obj) {
|
|
44
|
-
return res.status(404).json({ error: 'Object not found' })
|
|
45
|
-
}
|
|
46
|
-
res.json(obj.dataObject.toJSON())
|
|
47
|
-
} catch (e) {
|
|
48
|
-
Backend.error(`[API Error] GET /${req.params.id}: ${(e as Error).message}`)
|
|
49
|
-
res.status(500).json({ error: (e as Error).message })
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// U: Update
|
|
55
|
-
if (methods.includes('UPDATE')) {
|
|
56
|
-
const putPath = path.endsWith('/') ? `${path}:id` : `${path}/:id`
|
|
57
|
-
router.put(putPath, async (req, res) => {
|
|
58
|
-
try {
|
|
59
|
-
const data = req.body
|
|
60
|
-
const obj = await (ModelClass as any).fromBackend(req.params.id)
|
|
61
|
-
|
|
62
|
-
if (!obj) {
|
|
63
|
-
return res.status(404).json({ error: 'Object not found' })
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
Object.keys(data).forEach((key) => {
|
|
67
|
-
if (obj.has(key)) {
|
|
68
|
-
obj.set(key, data[key])
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
obj.validate()
|
|
73
|
-
await obj.save()
|
|
74
|
-
res.json(obj.dataObject.toJSON())
|
|
75
|
-
} catch (e: any) {
|
|
76
|
-
if (e.name === 'ValidationError') {
|
|
77
|
-
res.status(400).json({ error: e.message, validationErrors: e.errors })
|
|
78
|
-
} else {
|
|
79
|
-
Backend.error(`[API Error] PUT /${req.params.id}: ${e.message}`)
|
|
80
|
-
res.status(400).json({ error: e.message })
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// D: Delete
|
|
87
|
-
if (methods.includes('DELETE')) {
|
|
88
|
-
const deletePath = path.endsWith('/') ? `${path}:id` : `${path}/:id`
|
|
89
|
-
router.delete(deletePath, async (req, res) => {
|
|
90
|
-
try {
|
|
91
|
-
const obj = await (ModelClass as any).fromBackend(req.params.id)
|
|
92
|
-
|
|
93
|
-
if (!obj) {
|
|
94
|
-
return res.status(404).json({ error: 'Object not found' })
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
await obj.delete()
|
|
98
|
-
res.json({ success: true })
|
|
99
|
-
} catch (e) {
|
|
100
|
-
Backend.error(`[API Error] DELETE /${req.params.id}: ${(e as Error).message}`)
|
|
101
|
-
res.status(500).json({ error: (e as Error).message })
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { EndpointHandler, ServerAdapter, EndpointOptions } from '@quatrain/api'
|
|
2
|
-
import { BaseObject, DataObjectClass } from '@quatrain/core'
|
|
3
|
-
import { Backend } from '@quatrain/backend'
|
|
4
|
-
|
|
5
|
-
export const ListEndpoint = (ModelClass: typeof BaseObject): EndpointHandler => {
|
|
6
|
-
return (router: ServerAdapter, path: string, options: EndpointOptions) => {
|
|
7
|
-
|
|
8
|
-
router.get(path, async (req, res) => {
|
|
9
|
-
try {
|
|
10
|
-
let q = (ModelClass as any).query()
|
|
11
|
-
|
|
12
|
-
// Apply all query parameters as exact match filters
|
|
13
|
-
for (const [key, value] of Object.entries(req.query)) {
|
|
14
|
-
q = q.where(key, value, 'equals')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const results = await q.execute('dataObjects')
|
|
18
|
-
|
|
19
|
-
res.json({
|
|
20
|
-
items: results.items.map((r: DataObjectClass<any>) => r.toJSON()),
|
|
21
|
-
meta: results.meta
|
|
22
|
-
})
|
|
23
|
-
} catch (e) {
|
|
24
|
-
Backend.error(`[API Error] GET (List): ${(e as Error).message}`)
|
|
25
|
-
res.status(500).json({ error: (e as Error).message })
|
|
26
|
-
}
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { EndpointHandler, ServerAdapter, EndpointOptions } from '@quatrain/api'
|
|
2
|
-
import { BaseObject, DataObjectClass } from '@quatrain/core'
|
|
3
|
-
import { Backend } from '@quatrain/backend'
|
|
4
|
-
|
|
5
|
-
export const ValuesEndpoint = (ModelClass: typeof BaseObject): EndpointHandler => {
|
|
6
|
-
return (router: ServerAdapter, path: string, options: EndpointOptions) => {
|
|
7
|
-
// Create the endpoint route, assuming 'path' is something like '/api/users'
|
|
8
|
-
// We'll map it to '/api/users/values'
|
|
9
|
-
const valuesPath = path.endsWith('/') ? `${path}values` : `${path}/values`
|
|
10
|
-
|
|
11
|
-
router.get(valuesPath, async (req, res) => {
|
|
12
|
-
try {
|
|
13
|
-
const results = await (ModelClass as any).query()
|
|
14
|
-
.where('status', 'deleted', 'notEquals')
|
|
15
|
-
.execute('dataObjects')
|
|
16
|
-
|
|
17
|
-
// Identify exposed fields from PROPS_DEFINITION
|
|
18
|
-
const propsDef = (ModelClass as any).PROPS_DEFINITION || []
|
|
19
|
-
const exposedFields = propsDef
|
|
20
|
-
.filter((p: any) => p.htmlType && p.htmlType !== 'off')
|
|
21
|
-
.map((p: any) => p.name)
|
|
22
|
-
|
|
23
|
-
const searchFields = ['name', ...propsDef
|
|
24
|
-
.filter((p: any) => p.type === 'StringProperty' && p.fullSearch === true)
|
|
25
|
-
.map((p: any) => p.name)]
|
|
26
|
-
|
|
27
|
-
let values = results.items.map((dataObject: DataObjectClass<any>) => {
|
|
28
|
-
const payload: any = {
|
|
29
|
-
value: dataObject.uid,
|
|
30
|
-
name: dataObject.has('name') ? dataObject.get('name') : dataObject.uid
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
for (const field of exposedFields) {
|
|
34
|
-
if (dataObject.has(field)) {
|
|
35
|
-
payload[field] = dataObject.get(field)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const searchVals = searchFields.map(f => {
|
|
40
|
-
const val = dataObject.get(f)
|
|
41
|
-
return typeof val === 'string' ? val : ''
|
|
42
|
-
}).filter(Boolean)
|
|
43
|
-
payload._search = searchVals.join(' ').toLowerCase()
|
|
44
|
-
|
|
45
|
-
return payload
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const qParam = req.query.q as string
|
|
49
|
-
if (qParam) {
|
|
50
|
-
const searchStr = qParam.toLowerCase()
|
|
51
|
-
values = values.filter((payload: any) => {
|
|
52
|
-
return searchFields.some(field => {
|
|
53
|
-
const val = payload[field]
|
|
54
|
-
return typeof val === 'string' && val.toLowerCase().includes(searchStr)
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
res.json(values)
|
|
60
|
-
} catch (e) {
|
|
61
|
-
Backend.error(`[API Error] GET ${valuesPath}: ${(e as Error).message}`)
|
|
62
|
-
res.status(500).json({ error: (e as Error).message })
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
}
|