@opengis/fastify-table 2.4.5 → 2.4.7

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.
Files changed (32) hide show
  1. package/dist/module/core/select/core.user_mentioned.sql +1 -1
  2. package/dist/server/migrations/crm.sql +6 -1
  3. package/dist/server/migrations/oauth.sql.sql +77 -0
  4. package/dist/server/plugins/hook/index.js +39 -0
  5. package/dist/server/plugins/migration/index.d.ts +3 -0
  6. package/dist/server/plugins/migration/index.d.ts.map +1 -0
  7. package/dist/server/plugins/migration/index.js +5 -0
  8. package/dist/server/routes/auth/controllers/2factor/generate.js +38 -0
  9. package/dist/server/routes/auth/controllers/2factor/toggle.js +39 -0
  10. package/dist/server/routes/logger/controllers/utils/checkUserAccess.js +22 -0
  11. package/dist/server/routes/logger/controllers/utils/getRootDir.js +25 -0
  12. package/dist/server/routes/widget/controllers/widget.set.d.ts.map +1 -1
  13. package/dist/server/routes/widget/controllers/widget.set.js +5 -0
  14. package/dist/server/routes/widget/hook/onWidgetSet.d.ts +1 -1
  15. package/dist/server/routes/widget/hook/onWidgetSet.d.ts.map +1 -1
  16. package/dist/server/routes/widget/hook/onWidgetSet.js +8 -5
  17. package/dist/server/routes/widget/index.d.ts +1 -1
  18. package/dist/server/routes/widget/index.d.ts.map +1 -1
  19. package/dist/server/routes/widget/index.js +79 -3
  20. package/dist/server/routes/widget/services/widget.entity.del.d.ts +26 -0
  21. package/dist/server/routes/widget/services/widget.entity.del.d.ts.map +1 -0
  22. package/dist/server/routes/widget/services/widget.entity.del.js +79 -0
  23. package/dist/server/routes/widget/services/widget.entity.get.d.ts +39 -0
  24. package/dist/server/routes/widget/services/widget.entity.get.d.ts.map +1 -0
  25. package/dist/server/routes/widget/services/widget.entity.get.js +148 -0
  26. package/dist/server/routes/widget/services/widget.entity.set.d.ts +22 -0
  27. package/dist/server/routes/widget/services/widget.entity.set.d.ts.map +1 -0
  28. package/dist/server/routes/widget/services/widget.entity.set.js +131 -0
  29. package/dist/server/types/errors.d.ts +14 -0
  30. package/dist/server/types/errors.d.ts.map +1 -0
  31. package/dist/server/types/errors.js +4 -0
  32. package/package.json +1 -1
@@ -1,2 +1,2 @@
1
- select uid, coalesce(sur_name,'')||coalesce(' '||user_name,'') as text, email from admin.users
1
+ select uid, coalesce(sur_name,'')||coalesce(' '||user_name,'') as text, email from admin.users
2
2
  where enabled order by coalesce(sur_name,'')||coalesce(' '||user_name,'')
@@ -46,6 +46,7 @@ COMMENT ON COLUMN crm.notifications.body is 'Зміст повідомлення
46
46
  COMMENT ON COLUMN crm.notifications.link is 'Посилання на об''єкт';
47
47
  COMMENT ON COLUMN crm.notifications.author_id is 'ID користувача автора повідомлення';
48
48
  COMMENT ON COLUMN crm.notifications.entity_id is 'ID на об''єкту';
49
+ COMMENT ON COLUMN crm.notifications.entity_id is 'Сутність об''єкту';
49
50
 
50
51
  -- crm.files
51
52
  -- DROP TABLE IF EXISTS crm.files;
@@ -159,7 +160,11 @@ CREATE TABLE if not exists crm.reactions (
159
160
  reaction_id text default next_id() PRIMARY KEY,
160
161
  created_by text NOT NULL,
161
162
  entity_id text NOT NULL,
163
+ entity_type text,
162
164
  reaction_type text NOT NULL check (reaction_type in ('like', 'love', 'hate', 'wow', 'sad', 'angry')),
163
165
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
164
166
  FOREIGN KEY (created_by) REFERENCES admin.users(uid)
165
- );
167
+ );
168
+
169
+ alter table crm.reactions add column if not exists entity_type text;
170
+ alter table crm.notifications add column if not exists entity_type text;
@@ -0,0 +1,77 @@
1
+ CREATE schema if not exists oauth;
2
+
3
+ CREATE TABLE if not exists oauth.clients (
4
+ client_id text PRIMARY KEY DEFAULT next_id(), -- ID клієнта (публічний ідентифікатор)
5
+ client_secret_hash text, -- Хеш секрету (NULL для public-клієнтів)
6
+ name text NOT NULL, -- Назва застосунку
7
+ type text NOT NULL CHECK (type IN ('public','confidential')),
8
+ token_endpoint_auth_method text NOT NULL CHECK (token_endpoint_auth_method IN ('client_secret_basic','client_secret_post','private_key_jwt','none')),
9
+ owner_user_id text, -- Власник/адміністратор клієнта (посилання на users.id or other id)
10
+
11
+ redirect_uris text[], -- Дозволені redirect_uri
12
+ grant_types text[] CHECK (case when grant_types is not null then grant_types <@ ARRAY['authorization_code','refresh_token','client_credentials','device_code']::text[] else true end),
13
+ require_pkce boolean NOT NULL DEFAULT true,
14
+ scopes text[],
15
+ allowed_cors_origins text[],
16
+ jwks jsonb, -- Вбудований JWK Set (опційно)
17
+
18
+ created_at timestamptz NOT NULL DEFAULT now(),
19
+ updated_at timestamptz NOT NULL DEFAULT now()
20
+ );
21
+
22
+ CREATE TABLE if not exists oauth.tokens (
23
+ id text PRIMARY KEY DEFAULT next_id(),
24
+ token_type text NOT NULL CHECK (token_type IN ('access','refresh')),
25
+ token_hash text NOT NULL UNIQUE, -- Argon2/bcrypt/SCrypt (хеш у застосунку)
26
+ token_hint text, -- останні 6-8 символів для діагностики (необов’язково)
27
+ jti text UNIQUE, -- JWT ID, якщо токен — JWT
28
+ client_id text NOT NULL REFERENCES oauth.clients(client_id) ON DELETE CASCADE,
29
+ user_id text, -- NULL для client_credentials
30
+ issuer text, -- iss
31
+ scopes text[],
32
+ claims jsonb, -- додаткові клейми
33
+ issued_at timestamptz NOT NULL DEFAULT now(),
34
+ expires_at timestamptz NOT NULL,
35
+ revoked_at timestamptz,
36
+ revocation_reason text,
37
+ ip inet -- IP видачі/використання (опційно)
38
+ );
39
+
40
+ COMMENT ON SCHEMA oauth IS 'Schema for OAuth2 / OpenID Connect clients and tokens';
41
+
42
+ -- Comments for oauth.clients
43
+ COMMENT ON TABLE oauth.clients IS 'OAuth 2.0 clients (applications) that can request tokens';
44
+
45
+ COMMENT ON COLUMN oauth.clients.client_id IS 'Client identifier (public ID, generated by next_id())';
46
+ COMMENT ON COLUMN oauth.clients.client_secret_hash IS 'Hashed client secret (NULL for public clients)';
47
+ COMMENT ON COLUMN oauth.clients.name IS 'Name of the application/client';
48
+ COMMENT ON COLUMN oauth.clients.type IS 'Client type: public or confidential';
49
+ COMMENT ON COLUMN oauth.clients.token_endpoint_auth_method IS 'Authentication method at token endpoint (client_secret_basic, client_secret_post, private_key_jwt, none)';
50
+ COMMENT ON COLUMN oauth.clients.owner_user_id IS 'Owner/administrator of the client (reference to users.id or external id)';
51
+ COMMENT ON COLUMN oauth.clients.redirect_uris IS 'Allowed redirect URIs';
52
+ COMMENT ON COLUMN oauth.clients.grant_types IS 'Allowed grant types (authorization_code, refresh_token, client_credentials, device_code)';
53
+ COMMENT ON COLUMN oauth.clients.require_pkce IS 'Whether PKCE is required (default true)';
54
+ COMMENT ON COLUMN oauth.clients.scopes IS 'Allowed OAuth2 scopes';
55
+ COMMENT ON COLUMN oauth.clients.allowed_cors_origins IS 'Allowed CORS origins for browser-based apps';
56
+ COMMENT ON COLUMN oauth.clients.jwks IS 'Embedded JSON Web Key Set (optional)';
57
+ COMMENT ON COLUMN oauth.clients.created_at IS 'Creation timestamp';
58
+ COMMENT ON COLUMN oauth.clients.updated_at IS 'Last update timestamp';
59
+
60
+ -- Comments for oauth.tokens
61
+ COMMENT ON TABLE oauth.tokens IS 'Issued OAuth 2.0 tokens (access or refresh)';
62
+
63
+ COMMENT ON COLUMN oauth.tokens.id IS 'Internal token ID (generated by next_id())';
64
+ COMMENT ON COLUMN oauth.tokens.token_type IS 'Type of token: access or refresh';
65
+ COMMENT ON COLUMN oauth.tokens.token_hash IS 'Secure hash of the token (Argon2/bcrypt/SCrypt)';
66
+ COMMENT ON COLUMN oauth.tokens.token_hint IS 'Optional hint (last 6–8 characters of token) for diagnostics';
67
+ COMMENT ON COLUMN oauth.tokens.jti IS 'JWT ID if token is a JWT (unique)';
68
+ COMMENT ON COLUMN oauth.tokens.client_id IS 'Reference to oauth.clients (issuing client)';
69
+ COMMENT ON COLUMN oauth.tokens.user_id IS 'User ID if bound to user (NULL for client_credentials flow)';
70
+ COMMENT ON COLUMN oauth.tokens.issuer IS 'Token issuer (iss claim)';
71
+ COMMENT ON COLUMN oauth.tokens.scopes IS 'Granted OAuth2 scopes for this token';
72
+ COMMENT ON COLUMN oauth.tokens.claims IS 'Additional claims (JSONB)';
73
+ COMMENT ON COLUMN oauth.tokens.issued_at IS 'Timestamp when issued';
74
+ COMMENT ON COLUMN oauth.tokens.expires_at IS 'Timestamp when token expires';
75
+ COMMENT ON COLUMN oauth.tokens.revoked_at IS 'Timestamp when revoked';
76
+ COMMENT ON COLUMN oauth.tokens.revocation_reason IS 'Reason for revocation (if any)';
77
+ COMMENT ON COLUMN oauth.tokens.ip IS 'IP address of issuance/usage (optional)';
@@ -0,0 +1,39 @@
1
+ import config from "../../../config.js";
2
+ export const hookList = {};
3
+ export async function applyHook(name, data) {
4
+ if (config.trace)
5
+ console.log("applyHook", name);
6
+ if (!hookList[name]?.length)
7
+ return null;
8
+ const result = {};
9
+ await Promise.all(hookList[name].map(async (hook) => {
10
+ const hookData = await hook({ ...data, config });
11
+ if (hookData) {
12
+ if (config.trace)
13
+ console.log("applyHook", name, hookData);
14
+ Object.assign(result, hookData);
15
+ }
16
+ })).catch((err) => {
17
+ console.error("applyHook", name, err.toString());
18
+ });
19
+ if (Object.keys(result).length) {
20
+ return result;
21
+ }
22
+ return null;
23
+ }
24
+ export function addHook(name, fn) {
25
+ if (!hookList[name]) {
26
+ hookList[name] = [];
27
+ }
28
+ if (config.trace)
29
+ console.log("addHook", name);
30
+ hookList[name].push(fn);
31
+ }
32
+ export function applyHookSync(name, data) {
33
+ if (!hookList[name]?.length)
34
+ return null;
35
+ if (config.trace)
36
+ console.log("applyHookSync", name);
37
+ const hookData = hookList[name].map((hook) => hook(data))[0];
38
+ return hookData;
39
+ }
@@ -0,0 +1,3 @@
1
+ declare function plugin(): Promise<void>;
2
+ export default plugin;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../server/plugins/migration/index.ts"],"names":[],"mappings":"AAEA,iBAAe,MAAM,kBAEpB;AAED,eAAe,MAAM,CAAC"}
@@ -0,0 +1,5 @@
1
+ // import execMigrations from './funcs/exec.migrations.js';
2
+ async function plugin() {
3
+ // fastify.decorate('execMigrations', execMigrations);
4
+ }
5
+ export default plugin;
@@ -0,0 +1,38 @@
1
+ import config from "../../../../../config.js";
2
+ import pgClients from "../../../../plugins/pg/pgClients.js";
3
+ import { generate } from "./providers/totp.js";
4
+ /**
5
+ * Генерація secret для двохфакторної авторизації користувача
6
+ *
7
+ * @method GET
8
+ * @summary Генерація user secret для двохфакторної авторизації
9
+ * @priority 3
10
+ * @alias generate
11
+ * @type api
12
+ * @tag auth
13
+ * @requires 2fa
14
+ * @errors 500
15
+ * @returns {Number} status Номер помилки
16
+ * @returns {String|Object} error Опис помилки
17
+ * @returns {String|Object} message Повідомлення про успішне виконання або об'єкт з параметрами
18
+ */
19
+ export default async function generateFunction({ pg = pgClients.client, user = {} }, reply) {
20
+ if (!user?.uid) {
21
+ return reply.status(401).send("unauthorized");
22
+ }
23
+ const { uid } = user;
24
+ if (!config?.auth?.["2factor"]) {
25
+ return reply.status(400).send("2fa not enabled");
26
+ }
27
+ if (!config.pg) {
28
+ return reply.status(400).send("empty pg");
29
+ }
30
+ if (!uid) {
31
+ return reply.status(401).send("access restricted: unauthorized");
32
+ }
33
+ const res = await generate({ pg, uid });
34
+ if (res?.enabled) {
35
+ return reply.status(400).send("already created 2fa");
36
+ }
37
+ return reply.status(200).send(res);
38
+ }
@@ -0,0 +1,39 @@
1
+ import config from '../../../../../config.js';
2
+ import pgClients from '../../../../plugins/pg/pgClients.js';
3
+ import { toggle } from './providers/totp.js';
4
+ /**
5
+ * Включення/виключення двохфакторної авторизації для користувача
6
+ *
7
+ * @method GET
8
+ * @summary Включення/виключення двохфакторної авторизації
9
+ * @priority 2
10
+ * @alias toggle
11
+ * @type api
12
+ * @tag auth
13
+ * @requires 2fa
14
+ * @errors 500
15
+ * @returns {Number} status Номер помилки
16
+ * @returns {String|Object} error Опис помилки
17
+ * @returns {String|Object} message Повідомлення про успішне виконання або об'єкт з параметрами
18
+ */
19
+ export default async function toggleFunction(req, reply) {
20
+ const { pg = pgClients.client, session = {}, query = {}, } = req;
21
+ const { uid } = session?.passport?.user || {};
22
+ const { code, enable } = query;
23
+ if (!config.pg) {
24
+ return reply.status(400).send('empty pg');
25
+ }
26
+ if (!uid) {
27
+ return reply.status(401).send('access restricted: unauthorized');
28
+ }
29
+ if (!code) {
30
+ return reply.status(400).send('param "code" is required');
31
+ }
32
+ if (!Object.hasOwn(query, 'enable')) {
33
+ return reply.status(400).send('param "enable" is required');
34
+ }
35
+ const data = await toggle({
36
+ pg, code, enable: enable === 'true', uid,
37
+ });
38
+ return reply.status(200).send(data);
39
+ }
@@ -0,0 +1,22 @@
1
+ import config from "../../../../../config.js";
2
+ const { accessToken = "0NWcGQxKRP8AsRxD" } = config.auth || {};
3
+ /**
4
+ *
5
+ * @summary check user access to logger interface - per admin user type or user group
6
+ * @returns {Object} message, status
7
+ */
8
+ export default function checkUserAccess({ user = {}, token, }) {
9
+ if (token && token === accessToken) {
10
+ return { message: "access granted", status: 200 };
11
+ }
12
+ // console.log(user);
13
+ if (!user.user_type?.includes?.("admin") &&
14
+ !config?.local &&
15
+ !config.auth?.disable) {
16
+ return { message: "access restricted", status: 403 };
17
+ }
18
+ /* if (!['admin', 'superadmin']?.includes(user.user_type) && count === '0') {
19
+ return { message: 'access restricted', status: 403 };
20
+ } */
21
+ return { message: "access granted", status: 200 };
22
+ }
@@ -0,0 +1,25 @@
1
+ /* eslint-disable no-console */
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import config from "../../../../../config.js";
5
+ // import { existsSync } from 'fs';
6
+ let logDir = null;
7
+ export default function getRootDir() {
8
+ // absolute / relative path
9
+ if (logDir)
10
+ return logDir;
11
+ const file = ["config.json", "/data/local/config.json"].find((el) => fs.existsSync(el) ? el : null);
12
+ const root = file === "config.json" ? process.cwd() : "/data/local";
13
+ logDir = config.logDir || path.join(root, config.log?.dir || "log");
14
+ console.log({ logDir });
15
+ return logDir;
16
+ // windows debug support
17
+ /* const customLogDir = process.cwd().includes(':') ? 'c:/data/local' : '/data/local';
18
+ // docker default path
19
+ if (existsSync(customLogDir)) {
20
+ return path.join(customLogDir, config.folder || '', 'log');
21
+ }
22
+
23
+ // non-docker default path
24
+ return path.join(config.root || '/data/local', config.folder || '', 'log'); */
25
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"widget.set.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/controllers/widget.set.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAqC5C,wBAA8B,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY;;;;;;GAqHpE"}
1
+ {"version":3,"file":"widget.set.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/controllers/widget.set.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAqC5C,wBAA8B,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY;;;;;;GA8HpE"}
@@ -109,6 +109,11 @@ export default async function widgetSet(req, reply) {
109
109
  data,
110
110
  uid: user?.uid,
111
111
  });
112
+ // put w/ out file
113
+ if (type === "gallery" && body?.ismain && id) {
114
+ await pg.query(`update crm.files set ismain=false
115
+ where entity_id=$1 and file_id<>$2`, [objectid, id]);
116
+ }
112
117
  return reply.status(200).send({
113
118
  rowCount: result.rowCount,
114
119
  data: "ok",
@@ -1,2 +1,2 @@
1
- export default function onWidgetSet({ pg, id, objectid, type, payload }: any): Promise<null>;
1
+ export default function onWidgetSet({ pg, id, objectid, entityType, type, payload, }: any): Promise<null>;
2
2
  //# sourceMappingURL=onWidgetSet.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"onWidgetSet.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/hook/onWidgetSet.ts"],"names":[],"mappings":"AAEA,wBAA8B,WAAW,CAAC,EACxC,EAAqB,EACrB,EAAE,EACF,QAAQ,EACR,IAAI,EACJ,OAAY,EACb,EAAE,GAAG,iBAQL"}
1
+ {"version":3,"file":"onWidgetSet.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/hook/onWidgetSet.ts"],"names":[],"mappings":"AAEA,wBAA8B,WAAW,CAAC,EACxC,EAAqB,EACrB,EAAE,EACF,QAAQ,EACR,UAAU,EACV,IAAI,EACJ,OAAY,GACb,EAAE,GAAG,iBAiBL"}
@@ -1,10 +1,13 @@
1
- import pgClients from '../../../plugins/pg/pgClients.js';
2
- export default async function onWidgetSet({ pg = pgClients.client, id, objectid, type, payload = {} }) {
3
- if (!id || !objectid || type !== 'gallery') {
1
+ import pgClients from "../../../plugins/pg/pgClients.js";
2
+ export default async function onWidgetSet({ pg = pgClients.client, id, objectid, entityType, type, payload = {}, }) {
3
+ if (!id || !objectid || type !== "gallery") {
4
4
  return null;
5
5
  }
6
- if (payload?.ismain) {
7
- await pg.query('update crm.files set ismain=false where entity_id=$1 and file_id<>$2', [objectid, id]);
6
+ if (payload?.ismain && !entityType) {
7
+ await pg.query("update crm.files set ismain=false where entity_id=$1 and file_id<>$2", [objectid, id]);
8
+ }
9
+ if (payload?.ismain && entityType) {
10
+ await pg.query("update crm.files set ismain=false where entity_id=$1 and entity_type=$2 and file_id<>$3", [objectid, entityType, id]);
8
11
  }
9
12
  return null;
10
13
  }
@@ -1,2 +1,2 @@
1
- export default function route(app: any, opt?: any): void;
1
+ export default function route(app: any): void;
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../server/routes/widget/index.ts"],"names":[],"mappings":"AA2CA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,GAAE,GAAQ,QAKpD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../server/routes/widget/index.ts"],"names":[],"mappings":"AAkDA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,GAAG,EAAE,GAAG,QAmHrC"}
@@ -4,6 +4,10 @@ import widgetSet from "./controllers/widget.set.js";
4
4
  import widgetGet from "./controllers/widget.get.js";
5
5
  import fileEdit from "./controllers/file.edit.js";
6
6
  import onWidgetSet from "./hook/onWidgetSet.js";
7
+ import widgetEntityGet from "./services/widget.entity.get.js";
8
+ import widgetEntitySet from "./services/widget.entity.set.js";
9
+ import widgetEntityDel from "./services/widget.entity.del.js";
10
+ import uploadMultiPart from "../../plugins/file/uploadMultiPart.js";
7
11
  const tags = ["core", "widget"];
8
12
  const tableSchema = {
9
13
  params: {
@@ -20,6 +24,7 @@ const tableSchema = {
20
24
  "reaction",
21
25
  ],
22
26
  },
27
+ entityType: { type: "string", pattern: "^([\\w\\d_.]+)$" },
23
28
  objectid: { type: "string", pattern: "^([\\d\\w]+)$" },
24
29
  id: { type: "string", pattern: "^([\\d\\w]+)$" },
25
30
  },
@@ -34,9 +39,80 @@ const tableSchema = {
34
39
  addHook("onWidgetSet", onWidgetSet);
35
40
  const policy = "L0";
36
41
  const params = { config: { tags, policy }, schema: tableSchema };
37
- export default function route(app, opt = {}) {
38
- app.delete("/widget/:type/:objectid/:id", params, widgetDel);
42
+ export default function route(app) {
43
+ app.get("/v2/widget/:type/:entityType/:objectid", params, async (req) => {
44
+ const result = await widgetEntityGet({
45
+ type: req.params.type,
46
+ entityType: req.params.entityType,
47
+ objectid: req.params.objectid,
48
+ uid: req.user?.uid,
49
+ userName: req.user?.user_name,
50
+ debug: req.query.debug && req.user?.user_type?.includes?.("admin"),
51
+ }, req.pg);
52
+ return result;
53
+ });
54
+ app.post("/v2/widget/:type/:entityType/:objectid", params, async (req) => {
55
+ if (["gallery", "file"].includes(req.params.type) &&
56
+ req.headers["content-type"]?.startsWith("multipart/form-data")) {
57
+ const file = await uploadMultiPart(req);
58
+ const result = await widgetEntitySet({
59
+ type: req.params.type,
60
+ entityType: req.params.entityType,
61
+ objectid: req.params.objectid,
62
+ uid: req.user?.uid,
63
+ file,
64
+ }, req.pg);
65
+ return result;
66
+ }
67
+ const result = await widgetEntitySet({
68
+ type: req.params.type,
69
+ entityType: req.params.entityType,
70
+ objectid: req.params.objectid,
71
+ uid: req.user?.uid,
72
+ reqPath: req.path,
73
+ body: req.body,
74
+ }, req.pg);
75
+ return result;
76
+ });
77
+ app.put("/v2/widget/:type/:entityType/:objectid/:id", params, async (req) => {
78
+ if (["gallery", "file"].includes(req.params.type) &&
79
+ req.headers["content-type"]?.startsWith("multipart/form-data")) {
80
+ const file = await uploadMultiPart(req);
81
+ const result = await widgetEntitySet({
82
+ type: req.params.type,
83
+ entityType: req.params.entityType,
84
+ objectid: req.params.objectid,
85
+ id: req.params.id,
86
+ uid: req.user?.uid,
87
+ file,
88
+ }, req.pg);
89
+ return result;
90
+ }
91
+ const result = await widgetEntitySet({
92
+ type: req.params.type,
93
+ entityType: req.params.entityType,
94
+ objectid: req.params.objectid,
95
+ id: req.params.id,
96
+ uid: req.user?.uid,
97
+ reqPath: req.path,
98
+ body: req.body,
99
+ }, req.pg);
100
+ return result;
101
+ });
102
+ app.delete("/v2/widget/:type/:entityType/:objectid/:id", params, async (req) => {
103
+ const result = await widgetEntityDel({
104
+ type: req.params.type,
105
+ entityType: req.params.entityType,
106
+ objectid: req.params.objectid,
107
+ id: req.params.id,
108
+ uid: req.user?.uid,
109
+ userName: req.user?.user_name,
110
+ }, req.pg);
111
+ return result;
112
+ });
113
+ app.get("/widget/:type/:objectid", params, widgetGet);
39
114
  app.post("/widget/:type/:objectid/:id?", params, widgetSet);
115
+ // app.put("/widget/:type/:objectid/:id", params, widgetSet);
116
+ app.delete("/widget/:type/:objectid/:id", params, widgetDel);
40
117
  app.put("/file-edit/:id", params, fileEdit);
41
- app.get("/widget/:type/:objectid", params, widgetGet);
42
118
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ *
3
+ * @method DELETE
4
+ * @summary CRM дані для обраного віджета.
5
+ * @priority 2
6
+ * @tag table
7
+ * @type api
8
+ * @param {String} id Ідентифікатор child рядка (файлу, зображення, коментаря тощо)
9
+ * @param {Any} objectid ID parent рядка в БД, primary key
10
+ * @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
11
+ * @param {String} type Тип віджету
12
+ * @errors 400, 500
13
+ * @returns {Number} status Номер помилки
14
+ * @returns {String|Object} error Опис помилки
15
+ * @returns {String|Object} message Повідомлення про успішне виконання або об'єкт з параметрами
16
+ */
17
+ export default function widgetEntityDel({ type, entityType, objectid, id, uid, userName }: any, pg?: any): Promise<{
18
+ data: {
19
+ id: any;
20
+ };
21
+ user: {
22
+ uid: any;
23
+ name: any;
24
+ };
25
+ }>;
26
+ //# sourceMappingURL=widget.entity.del.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.entity.del.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/services/widget.entity.del.ts"],"names":[],"mappings":"AAiCA;;;;;;;;;;;;;;;GAeG;AAEH,wBAA8B,eAAe,CAC3C,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,GAAG,EACtD,EAAE,MAAmB;;;;;;;;GAwEtB"}
@@ -0,0 +1,79 @@
1
+ import config from "../../../../config.js";
2
+ import isFileExists from "../../../plugins/file/isFileExists.js";
3
+ import logChanges from "../../../plugins/crud/funcs/utils/logChanges.js";
4
+ import pgClients from "../../../plugins/pg/pgClients.js";
5
+ import { BadRequestError, ForbiddenError, UnauthorizedError, } from "../../../../errors.js";
6
+ const isAdmin = () => process.env.NODE_ENV === "admin" || config.admin;
7
+ async function checkAccess({ pg, objectid, entityType, id, }) {
8
+ const { uid, filepath } = await pg
9
+ .query("select uid, file_path as filepath from crm.files where entity_id=$1 and entity_type=$2 and entity_type=$2 and file_id=$3", [objectid, entityType, id])
10
+ .then((el) => el.rows?.[0] || {});
11
+ return { uid, exists: filepath ? await isFileExists(filepath) : null };
12
+ }
13
+ /**
14
+ *
15
+ * @method DELETE
16
+ * @summary CRM дані для обраного віджета.
17
+ * @priority 2
18
+ * @tag table
19
+ * @type api
20
+ * @param {String} id Ідентифікатор child рядка (файлу, зображення, коментаря тощо)
21
+ * @param {Any} objectid ID parent рядка в БД, primary key
22
+ * @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
23
+ * @param {String} type Тип віджету
24
+ * @errors 400, 500
25
+ * @returns {Number} status Номер помилки
26
+ * @returns {String|Object} error Опис помилки
27
+ * @returns {String|Object} message Повідомлення про успішне виконання або об'єкт з параметрами
28
+ */
29
+ export default async function widgetEntityDel({ type, entityType, objectid, id, uid, userName }, pg = pgClients.client) {
30
+ if (!uid) {
31
+ throw UnauthorizedError("unauthorized");
32
+ }
33
+ if (!entityType) {
34
+ throw BadRequestError("not enough params: entity type");
35
+ }
36
+ if (!objectid) {
37
+ throw BadRequestError("not enough params: object id");
38
+ }
39
+ if (!id && type !== "reaction") {
40
+ throw BadRequestError("not enough params: id");
41
+ }
42
+ // force delete db entry if file not exists
43
+ const { exists, uid: author } = ["file", "gallery"].includes(type)
44
+ ? await checkAccess({ pg, objectid, entityType, id })
45
+ : {};
46
+ if (exists && !isAdmin() && uid && author !== uid) {
47
+ throw ForbiddenError("access restricted: file exists, not an author");
48
+ }
49
+ const sqls = {
50
+ comment: `delete from crm.communications where entity_id=$1 and entity_type=$2 and ${isAdmin() ? "$3=$3" : "uid=$3"} and communication_id=$4`,
51
+ checklist: `delete from crm.checklists where entity_id=$1 and entity_type=$2 and ${isAdmin() ? "$3=$3" : "uid=$3"} and checklist_id=$4`,
52
+ file: `update crm.files set file_status=3 where entity_id=$1 and entity_type=$2 and ${!exists || isAdmin() ? "$3=$3" : "uid=$3"} and file_id=$4 returning uploaded_name`,
53
+ gallery: `update crm.files set file_status=3 where entity_id=$1 and entity_type=$2 and ${!exists || isAdmin() ? "$3=$3" : "uid=$3"} and file_id=$4 returning uploaded_name`,
54
+ reaction: `delete from crm.reactions where entity_id=$1 and entity_type=$2 and ${isAdmin() ? "$3=$3" : "created_by=$3"} and $3=$3 and reaction_id=$4 returning reaction_type`,
55
+ };
56
+ const q = sqls[type];
57
+ const table = {
58
+ comment: "crm.communications",
59
+ checklist: "crm.checklists",
60
+ file: "crm.files",
61
+ gallery: "crm.files",
62
+ reaction: "crm.reactions",
63
+ }[type];
64
+ if (!q || !table) {
65
+ throw BadRequestError("invalid widget type");
66
+ }
67
+ const rows = await pg
68
+ .query(q, [objectid, entityType, uid, id || ""])
69
+ .then((r) => r.rows || []);
70
+ await logChanges({
71
+ pg,
72
+ table,
73
+ id: type === "reaction" ? objectid : id,
74
+ data: rows[0],
75
+ uid,
76
+ type: "DELETE",
77
+ });
78
+ return { data: { id }, user: { uid, name: userName } };
79
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @method GET
3
+ * @type api
4
+ * @param {Any} objectid ID parent рядка в БД, primary key
5
+ * @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
6
+ * @param {String} type Тип віджету
7
+ */
8
+ export default function widgetEntityGet({ type, entityType, objectid, uid, userName, debug }: any, pg?: any): Promise<{
9
+ q: string;
10
+ type: any;
11
+ q1: string;
12
+ id: any;
13
+ data: any;
14
+ time?: undefined;
15
+ rows?: undefined;
16
+ user?: undefined;
17
+ objectid?: undefined;
18
+ } | {
19
+ time: {
20
+ rows: number;
21
+ data: number;
22
+ };
23
+ rows: any;
24
+ user: {
25
+ uid: any;
26
+ name: any;
27
+ };
28
+ data: {
29
+ author: any;
30
+ cdate: any;
31
+ edate: any;
32
+ };
33
+ objectid: any;
34
+ q?: undefined;
35
+ type?: undefined;
36
+ q1?: undefined;
37
+ id?: undefined;
38
+ }>;
39
+ //# sourceMappingURL=widget.entity.get.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.entity.get.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/services/widget.entity.get.ts"],"names":[],"mappings":"AAyBA;;;;;;GAMG;AAEH,wBAA8B,eAAe,CAC3C,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,EACzD,EAAE,MAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyKtB"}
@@ -0,0 +1,148 @@
1
+ import getMeta from "../../../plugins/pg/funcs/getMeta.js";
2
+ import pgClients from "../../../plugins/pg/pgClients.js";
3
+ import { BadRequestError, NotFoundError } from "../../../../errors.js";
4
+ const galleryExtList = [
5
+ "png",
6
+ "svg",
7
+ "jpg",
8
+ "jpeg",
9
+ "gif",
10
+ "mp4",
11
+ "mov",
12
+ "avi",
13
+ ];
14
+ const username = "coalesce(u.sur_name,'')||coalesce(' '||u.user_name,'') ||coalesce(' '||u.father_name,'')";
15
+ const pkList = {
16
+ comment: "communication_id",
17
+ checklist: "checklist_id",
18
+ file: "file_id",
19
+ gallery: "file_id",
20
+ reaction: "reaction_id",
21
+ };
22
+ /**
23
+ * @method GET
24
+ * @type api
25
+ * @param {Any} objectid ID parent рядка в БД, primary key
26
+ * @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
27
+ * @param {String} type Тип віджету
28
+ */
29
+ export default async function widgetEntityGet({ type, entityType, objectid, uid, userName, debug }, pg = pgClients.client) {
30
+ const time = [Date.now()];
31
+ if (!entityType) {
32
+ throw BadRequestError("not enough params: entity type");
33
+ }
34
+ if (!objectid) {
35
+ throw BadRequestError("not enough params: id");
36
+ }
37
+ const sqls = {
38
+ comment: pg.pk["admin.users"]
39
+ ? `select communication_id, entity_id, body, subject, c.cdate, c.uid,
40
+ ${username} as username, u.login, u.avatar
41
+ from crm.communications c
42
+ left join admin.users u on u.uid=c.uid
43
+ where entity_id=$1 and entity_type=$2 order by cdate desc`
44
+ : "select communication_id, entity_id, body, subject, cdate, uid from crm.communications where entity_id=$1 and entity_type=$2 order by cdate desc",
45
+ history: `SELECT change_id, entity_id, entity_type, change_type, change_date, a.change_user_id, a.uid, a.cdate, b.json_agg as changes,
46
+ ${username} as username, u.login, u.avatar
47
+ FROM log.table_changes a
48
+ left join admin.users u on a.change_user_id = u.uid
49
+ left join lateral(
50
+ select json_agg(row_to_json(q)) from (
51
+ select change_data_id, entity_key, value_new, value_old from log.table_changes_data
52
+ where change_id=a.change_id
53
+ )q
54
+ )b on 1=1
55
+ where a.change_type != 'INSERT' and b.json_agg is not null and ((entity_id=$1 and entity_type=$2) or entity_id in (
56
+ select communication_id from crm.communications where entity_id=$1 and entity_type=$2
57
+ union all select checklist_id from crm.checklists where entity_id=$1 and entity_type=$2
58
+ union all select file_id from crm.files where entity_id=$1 and entity_type=$2)
59
+ )
60
+ order by cdate desc limit 100`,
61
+ checklist: pg.pk["admin.users"]
62
+ ? `SELECT checklist_id, entity_id, subject, is_done, done_date, c.uid, c.cdate, ${username} as username, u.login, u.avatar
63
+ FROM crm.checklists c
64
+ left join admin.users u on u.uid=c.uid
65
+ where entity_id=$1 and entity_type=$2 order by cdate desc`
66
+ : "SELECT checklist_id, entity_id, subject, is_done, done_date, uid, cdate FROM crm.checklists where entity_id=$1 and entity_type=$2 order by cdate desc",
67
+ file: pg.pk["admin.users"]
68
+ ? `SELECT file_id, entity_id, entity_type, file_path, uploaded_name, ext, size, c.uid, c.cdate, file_type, c.ismain,
69
+ ${username} as username, u.login, isverified, u.avatar, u.uid as author, file_status
70
+ FROM crm.files c
71
+ left join admin.users u on u.uid=c.uid
72
+ where entity_id=$1 and entity_type=$2 and file_status<>3 order by cdate desc`
73
+ : `SELECT file_id, entity_id, entity_type, file_path, uploaded_name, ext, size, uid, cdate, file_type, ismain,
74
+ isverified, uid as author, file_status FROM crm.files c where entity_id=$1 and entity_type=$2 and file_status<>3 order by cdate desc`,
75
+ gallery: pg.pk["admin.users"]
76
+ ? `SELECT file_id, entity_id, entity_type, file_path, uploaded_name, ext, size, c.uid, c.cdate, file_type, c.ismain,
77
+ ${username} as username, u.login, u.avatar, isverified, u.avatar, c.uid as author, file_status
78
+ FROM crm.files c
79
+ left join admin.users u on u.uid=c.uid
80
+ where entity_id=$1 and entity_type=$2 and file_status<>3 and ext = any($3) order by cdate desc`
81
+ : `SELECT file_id, entity_id, entity_type, file_path, uploaded_name, ext, size, c.uid, c.cdate, file_type, ismain,
82
+ isverified, uid as author, file_status FROM crm.files c where entity_id=$1 and entity_type=$2 and file_status<>3 and ext = any($3) order by cdate desc`,
83
+ reaction: "SELECT count(*), reaction_type FROM crm.reactions c where entity_id=$1 and entity_type=$2 group by reaction_type",
84
+ };
85
+ const q = sqls[type];
86
+ if (!q) {
87
+ throw BadRequestError("invalid widget type");
88
+ }
89
+ /* rows */
90
+ const rows = await pg
91
+ .query(q, [objectid, entityType, type === "gallery" ? galleryExtList : null].filter(Boolean))
92
+ .then((el) => el.rows || []);
93
+ time.push(Date.now());
94
+ rows.forEach((row) => Object.assign(row, { username: row.username?.trim?.() || row.login }));
95
+ /* reactions */
96
+ const widgetPkey = pkList[type];
97
+ if (type === "comment" && widgetPkey) {
98
+ const reactions = rows.length && uid
99
+ ? await pg
100
+ .query("select json_object_agg(entity_id, reaction_type) from crm.reactions where entity_id=any($1) and entity_type=$2 and created_by=$3", [rows.map((row) => row[widgetPkey]), entityType, uid])
101
+ .then((el) => el.rows?.[0]?.json_object_agg || {})
102
+ : {};
103
+ rows
104
+ .filter((row) => reactions[row[widgetPkey]])
105
+ .forEach((row) => {
106
+ Object.assign(row, { reaction: reactions[row[widgetPkey]] });
107
+ });
108
+ }
109
+ /* Object info */
110
+ const { pk, columns = [] } = await getMeta({ pg, table: entityType });
111
+ const authorIdColumn = columns.find((col) => ["uid", "created_by"].includes(col.name))?.name;
112
+ if (!pk &&
113
+ type === "history" &&
114
+ process.env.NODE_ENV !== "test" &&
115
+ !process.env.VITEST) {
116
+ throw NotFoundError("log table not found");
117
+ }
118
+ const cdateColumn = columns.find((col) => ["cdate", "created_at"].includes(col.name))?.name;
119
+ const editorDateColumn = columns.find((col) => ["editor_date", "updated_at"].includes(col.name))?.name;
120
+ const q1 = `select ${username} as author, u.login ${cdateColumn ? `,a.${cdateColumn} as cdate` : ""} ${editorDateColumn ? `,a.${editorDateColumn} as editor_date` : ""} from ${entityType} a
121
+ left join admin.users u on a.${authorIdColumn}=u.uid where a.${pk}=$1 limit 1`;
122
+ const data = pg.pk["admin.users"] && pk && entityType
123
+ ? await pg
124
+ .query(q1, [objectid, type === "gallery" ? galleryExtList : null].filter(Boolean))
125
+ .then((el) => el.rows?.[0] || {})
126
+ : {};
127
+ time.push(Date.now());
128
+ if (debug) {
129
+ return {
130
+ q,
131
+ type,
132
+ q1,
133
+ id: objectid,
134
+ data,
135
+ };
136
+ }
137
+ return {
138
+ time: { rows: time[1] - time[0], data: time[2] - time[1] },
139
+ rows,
140
+ user: { uid, name: userName },
141
+ data: {
142
+ author: data?.author,
143
+ cdate: data?.cdate,
144
+ edate: data?.editor_date,
145
+ },
146
+ objectid,
147
+ };
148
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @method POST|PUT
3
+ * @type api
4
+ * @param {String} id Ідентифікатор child рядка (файлу, зображення, коментаря тощо)
5
+ * @param {Any} objectid ID parent рядка в БД, primary key
6
+ * @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
7
+ * @param {String} type Тип віджету
8
+ */
9
+ export default function widgetEntitySet({ type, entityType, objectid, id, uid, reqPath, file, body }: any, pg?: any): Promise<{
10
+ rowCount: number;
11
+ data: string;
12
+ command: string;
13
+ id: any;
14
+ entity_id: any;
15
+ } | {
16
+ rowCount: any;
17
+ data: string;
18
+ command: any;
19
+ id: any;
20
+ entity_id?: undefined;
21
+ }>;
22
+ //# sourceMappingURL=widget.entity.set.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.entity.set.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/services/widget.entity.set.ts"],"names":[],"mappings":"AAoCA;;;;;;;GAOG;AAEH,wBAA8B,eAAe,CAC3C,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,EACjE,EAAE,MAAmB;;;;;;;;;;;;GAiHtB"}
@@ -0,0 +1,131 @@
1
+ import path from "node:path";
2
+ import pgClients from "../../../plugins/pg/pgClients.js";
3
+ import getMeta from "../../../plugins/pg/funcs/getMeta.js";
4
+ import dataInsert from "../../../plugins/crud/funcs/dataInsert.js";
5
+ import dataUpdate from "../../../plugins/crud/funcs/dataUpdate.js";
6
+ import { applyHook } from "../../../../utils.js";
7
+ import { BadRequestError, NotFoundError } from "../../../../errors.js";
8
+ const tableList = {
9
+ comment: "crm.communications",
10
+ gallery: "crm.files",
11
+ file: "crm.files",
12
+ checklist: "crm.checklists",
13
+ reaction: "crm.reactions",
14
+ };
15
+ const pkList = {
16
+ comment: "communication_id",
17
+ checklist: "checklist_id",
18
+ file: "file_id",
19
+ gallery: "file_id",
20
+ reaction: "reaction_id",
21
+ };
22
+ const galleryExtList = [
23
+ "png",
24
+ "svg",
25
+ "jpg",
26
+ "jpeg",
27
+ "gif",
28
+ "mp4",
29
+ "mov",
30
+ "avi",
31
+ ];
32
+ /**
33
+ * @method POST|PUT
34
+ * @type api
35
+ * @param {String} id Ідентифікатор child рядка (файлу, зображення, коментаря тощо)
36
+ * @param {Any} objectid ID parent рядка в БД, primary key
37
+ * @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
38
+ * @param {String} type Тип віджету
39
+ */
40
+ export default async function widgetEntitySet({ type, entityType, objectid, id, uid, reqPath, file, body }, pg = pgClients.client) {
41
+ if (!pkList[type] || !tableList[type]) {
42
+ throw BadRequestError("param type not valid");
43
+ }
44
+ if (!entityType) {
45
+ throw BadRequestError("not enough params: entity type");
46
+ }
47
+ if (!objectid) {
48
+ throw BadRequestError("not enough params: id");
49
+ }
50
+ const table = tableList[type];
51
+ if (file) {
52
+ const extName = path
53
+ .extname(file.filepath || "")
54
+ .slice(1)
55
+ .toLowerCase();
56
+ const data = {
57
+ uploaded_name: file?.originalFilename
58
+ ?.toLocaleLowerCase()
59
+ ?.replace(/'/g, "''"),
60
+ file_path: file?.relativeFilepath?.replace(/\\/g, "/"),
61
+ ext: extName,
62
+ size: file?.size,
63
+ file_status: 1,
64
+ uid: uid || 1,
65
+ entity_id: objectid,
66
+ entity_type: entityType,
67
+ };
68
+ if (type === "gallery" && !galleryExtList.includes(extName.toLowerCase())) {
69
+ throw BadRequestError("invalid file extension");
70
+ }
71
+ const row = await dataInsert({
72
+ pg,
73
+ table: "crm.files",
74
+ data,
75
+ uid,
76
+ }).then((r) => r.rows?.[0]);
77
+ if (type === "gallery") {
78
+ await pg.query(`update crm.files set ismain=true
79
+ where entity_id=$1 and entity_type=$2
80
+ and file_id=$3
81
+ and (select count(*) = 0 from crm.files where entity_id=$1 and entity_type=$2 and ismain)`, [objectid, entityType, row?.file_id]);
82
+ }
83
+ return {
84
+ rowCount: 1,
85
+ data: "ok",
86
+ command: "UPLOAD",
87
+ id: row?.file_id,
88
+ entity_id: row?.entity_id,
89
+ };
90
+ }
91
+ const { pk } = await getMeta({ pg, table });
92
+ if (!pk) {
93
+ throw NotFoundError("table not found");
94
+ }
95
+ const data = { ...body, uid, entity_id: objectid, entity_type: entityType };
96
+ await applyHook("onWidgetSet", {
97
+ pg,
98
+ link: reqPath,
99
+ id,
100
+ objectid,
101
+ entityType,
102
+ user: { uid },
103
+ type,
104
+ payload: data,
105
+ });
106
+ const result = id
107
+ ? await dataUpdate({
108
+ pg,
109
+ table,
110
+ data,
111
+ id,
112
+ uid,
113
+ })
114
+ : await dataInsert({
115
+ pg,
116
+ table,
117
+ data,
118
+ uid,
119
+ });
120
+ // put w/ out file
121
+ if (type === "gallery" && body?.ismain && id) {
122
+ await pg.query(`update crm.files set ismain=false
123
+ where entity_id=$1 and entity_type=$2 and file_id<>$3`, [objectid, entityType, id]);
124
+ }
125
+ return {
126
+ rowCount: result.rowCount,
127
+ data: "ok",
128
+ command: result.command,
129
+ id: result.rows?.[0]?.[pkList[type]] || result?.[pkList[type]],
130
+ };
131
+ }
@@ -0,0 +1,14 @@
1
+ import createError from "@fastify/error";
2
+ export declare const NotFoundError: createError.FastifyErrorConstructor<{
3
+ code: "NOT_FOUND_ERROR";
4
+ statusCode: 404;
5
+ }, [any?, any?, any?]>;
6
+ export declare const BadRequestError: createError.FastifyErrorConstructor<{
7
+ code: "BAD_REQUEST_ERROR";
8
+ statusCode: 400;
9
+ }, [any?, any?, any?]>;
10
+ export declare const PayloadTooLargeError: createError.FastifyErrorConstructor<{
11
+ code: "PAYLOAD_TOO_LARGE_ERROR";
12
+ statusCode: 413;
13
+ }, [any?, any?, any?]>;
14
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../server/types/errors.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,gBAAgB,CAAC;AACzC,eAAO,MAAM,aAAa;;;sBAIzB,CAAC;AACF,eAAO,MAAM,eAAe;;;sBAI3B,CAAC;AACF,eAAO,MAAM,oBAAoB;;;sBAIhC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import createError from "@fastify/error";
2
+ export const NotFoundError = createError("NOT_FOUND_ERROR", "Resource not found: %s", 404);
3
+ export const BadRequestError = createError("BAD_REQUEST_ERROR", "Bad request: %s", 400);
4
+ export const PayloadTooLargeError = createError("PAYLOAD_TOO_LARGE_ERROR", "Payload Too Large: %s", 413);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "2.4.5",
3
+ "version": "2.4.7",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [