@opengis/fastify-table 2.0.12 → 2.0.14

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/config.js CHANGED
@@ -1,23 +1,26 @@
1
1
  /* eslint-disable no-console */
2
2
  import dotenv from "dotenv";
3
- dotenv.config(); // load .env for node, not required for bun
3
+ dotenv.config(); // ! load .env (workaround for node)
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import unflattenObject from "./server/plugins/util/funcs/unflattenObject.js";
6
6
  const fileName = ["config.json", "/data/local/config.json"].find((el) => existsSync(el) ? el : null);
7
7
  const config = fileName ? JSON.parse(readFileSync(fileName, "utf-8")) : {};
8
8
  const { skipKeys = ["windir"] } = config;
9
- // npm run dev === cross-env NODE_ENV=development
10
- // alt: node --env=development
11
9
  Object.assign(config, {
12
10
  storageList: {},
13
11
  allTemplates: config?.allTemplates || {},
14
12
  env: process.env?.NODE_ENV,
15
13
  });
14
+ if (existsSync(`.env.local`)) {
15
+ dotenv.config({ path: `.env.local` }); // ! load .env.local
16
+ }
16
17
  if (process.env?.NODE_ENV && existsSync(`.env.${process.env.NODE_ENV}`)) {
17
- dotenv.config({ path: `.env.${process.env.NODE_ENV}` }); // load .env.{{production}} etc. for node, not required for bun
18
+ dotenv.config({ path: `.env.${process.env.NODE_ENV}` }); // ! load .env.{{production}} etc.
19
+ }
20
+ if (process.env?.NODE_ENV && existsSync(`.env.${process.env.NODE_ENV}.local`)) {
21
+ dotenv.config({ path: `.env.${process.env.NODE_ENV}.local` }); // ! load .env.{{production}}.local etc.
18
22
  }
19
23
  function loadEnvConfig() {
20
- // node --env-file-if-exists=.env.dev --env-file-if-exists=.env server
21
24
  if (config.trace) {
22
25
  console.log(Object.keys(process.env));
23
26
  }
package/dist/index.js CHANGED
@@ -1,9 +1,18 @@
1
1
  import fp from "fastify-plugin";
2
2
  import path from "node:path";
3
- import { existsSync, readdirSync, readFileSync } from "node:fs";
3
+ import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, } from "node:fs";
4
+ import { createHash } from "node:crypto";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import proxy from "@fastify/http-proxy";
6
7
  import config from "./config.js";
8
+ import getTemplatePath from "./server/plugins/table/funcs/getTemplatePath.js";
9
+ import addHook from "./server/plugins/hook/addHook.js";
10
+ import getToken from "./server/plugins/crud/funcs/getToken.js";
11
+ import getTemplate from "./server/plugins/table/funcs/getTemplate.js";
12
+ import getRedis from "./server/plugins/redis/funcs/getRedis.js";
13
+ import logger from "./server/plugins/logger/getLogger.js";
14
+ import getMenu from "./server/routes/menu/controllers/getMenu.js";
15
+ import getSelectVal from "./server/plugins/table/funcs/metaFormat/getSelectVal.js";
7
16
  const { maxFileSize = 512 } = config;
8
17
  const { name: execName } = path.parse(process.argv0);
9
18
  // helpers
@@ -37,17 +46,221 @@ import widgetRoutes from "./server/routes/widget/index.js";
37
46
  import authRoutes from "./server/routes/auth/index.js";
38
47
  import fileRoutes from "./server/routes/file/index.js";
39
48
  import grpcRoutes from "./server/routes/grpc/index.js";
49
+ const { client } = pgClients;
50
+ const rclient = getRedis();
40
51
  // core templates && cls
41
52
  const filename = fileURLToPath(import.meta.url);
42
53
  const cwd = path.dirname(filename);
54
+ addHook("preForm", async ({ form, user }) => {
55
+ if (!user?.uid)
56
+ return null;
57
+ const opt = await getToken({
58
+ mode: "w",
59
+ token: form,
60
+ uid: user.uid,
61
+ json: 1,
62
+ });
63
+ return opt;
64
+ });
65
+ addHook("afterTable", async ({ table, res = {}, payload: rows = [], user = {}, }) => {
66
+ const loadTable = await getTemplate("table", table);
67
+ const { uid } = config?.auth?.disable || process.env.NODE_ENV !== "admin"
68
+ ? { uid: "1" }
69
+ : user;
70
+ if (!uid ||
71
+ !table ||
72
+ !client?.pk?.[table] ||
73
+ !rows.length ||
74
+ !loadTable?.table ||
75
+ !client?.pk?.["crm.extra_data"] ||
76
+ !client?.pk?.["admin.custom_column"])
77
+ return;
78
+ // admin.custom_column - user column data
79
+ const { rows: properties = [] } = await client.query(`select column_id, name, title, format, data from admin.custom_column
80
+ where _table and entity=$1 and uid=$2`, [table, uid]);
81
+ const extraColumnList = properties.map((row) => ({
82
+ id: row.column_id,
83
+ name: row.name,
84
+ title: row.title,
85
+ format: row.format,
86
+ data: row.data,
87
+ }));
88
+ if (!extraColumnList?.length)
89
+ return;
90
+ if (Array.isArray(res?.columns) && res?.columns?.length) {
91
+ extraColumnList.forEach((col) => res.columns.push(col));
92
+ }
93
+ const { rows: extraData = [] } = await client.query(`select object_id, json_object_agg( property_id, coalesce(value_date::text,value_text) ) as extra from crm.extra_data
94
+ where property_entity=$1 and property_id=any($2) and object_id=any($3) group by object_id`, [
95
+ table,
96
+ extraColumnList?.map((el) => el.id),
97
+ rows.map((el) => el.id),
98
+ ]);
99
+ if (!extraData?.length) {
100
+ // Object.assign(rows?.[0] || {}, { ...extraColumnList.reduce((acc, curr) => Object.assign(acc, { [curr.name]: null }), {}) });
101
+ return;
102
+ }
103
+ rows
104
+ .filter((row) => extraData.map((el) => el?.object_id).includes(row.id))
105
+ .forEach((row) => {
106
+ const { extra = {} } = extraData.find((el) => el.object_id === row.id);
107
+ Object.assign(row, {
108
+ ...Object.fromEntries(Object.entries(extra).map((el) => [
109
+ extraColumnList.find((col) => col.id === el[0]).name,
110
+ el[1],
111
+ ])),
112
+ });
113
+ });
114
+ // admin.custom_column - metaFormat
115
+ await Promise.all(extraColumnList
116
+ .filter((el) => el?.data)
117
+ .map(async (attr) => {
118
+ const values = [
119
+ ...new Set(rows?.map((el) => el[attr.name]).flat()),
120
+ ].filter((el) => el);
121
+ if (!values.length)
122
+ return;
123
+ const cls = await getSelectVal({ name: attr.data, values });
124
+ if (!cls)
125
+ return;
126
+ rows.forEach((el) => {
127
+ const val = el[attr.name]?.map?.((c) => cls[c] || c) ||
128
+ cls[el[attr.name]] ||
129
+ el[attr.name];
130
+ if (!val)
131
+ return;
132
+ Object.assign(el, {
133
+ [val?.color ? `${attr.name}_data` : `${attr.name}_text`]: val.color ? val : val.text || val,
134
+ });
135
+ });
136
+ }));
137
+ });
138
+ // extract table from form token for user columns - p.2 - read (refactor to global token)
139
+ addHook("preTemplate", async ({ name, type, user = {}, }) => {
140
+ if (!name || !type)
141
+ return;
142
+ const { uid } = config?.auth?.disable || process.env.NODE_ENV !== "admin"
143
+ ? { uid: "1" }
144
+ : user;
145
+ const tokenData = (await getToken({
146
+ uid,
147
+ token: name,
148
+ mode: "w",
149
+ json: 1,
150
+ })) || // edit?
151
+ (await getToken({
152
+ uid,
153
+ token: name,
154
+ mode: "a",
155
+ json: 1,
156
+ })) ||
157
+ {}; // add?
158
+ return { name: tokenData?.[type] };
159
+ });
160
+ addHook("afterTemplate", async ({ name, type, payload: data = {}, user = {}, }) => {
161
+ const { uid } = config?.auth?.disable || process.env.NODE_ENV !== "admin"
162
+ ? { uid: "1" }
163
+ : user;
164
+ // extract table from form token for user columns - p.1 - assign (refactor to global token)
165
+ if (!uid ||
166
+ !data ||
167
+ type !== "form" ||
168
+ !name ||
169
+ !client?.pk?.["admin.custom_column"])
170
+ return null;
171
+ const { form, id, table } = (await getToken({
172
+ uid,
173
+ token: name,
174
+ mode: "w",
175
+ json: 1,
176
+ })) || // edit?
177
+ (await getToken({
178
+ uid,
179
+ token: name,
180
+ mode: "a",
181
+ json: 1,
182
+ })) ||
183
+ {}; // add?
184
+ const { rows: properties = [] } = await client.query(`select name, title, format, data from admin.custom_column
185
+ where entity=$1 and uid=$2`, [table || name, uid]);
186
+ await Promise.all(properties.map(async (el) => {
187
+ const clsData = el.data
188
+ ? await getTemplate(["cls", "select"], el.data)
189
+ : undefined;
190
+ const type = clsData
191
+ ? "Select"
192
+ : { date: "DatePicker" }[el.format || ""] || "Text";
193
+ Object.assign(data?.schema || data || {}, {
194
+ [el.name]: {
195
+ type,
196
+ ua: el.title,
197
+ data: el.data,
198
+ options: type === "Select" && Array.isArray(clsData) && clsData?.length
199
+ ? clsData
200
+ : undefined,
201
+ extra: 1,
202
+ },
203
+ });
204
+ }));
205
+ });
206
+ addHook("afterUpdate", async ({ table, body = {}, payload: res = {}, user = {}, }) => {
207
+ const { uid } = config?.auth?.disable || process.env.NODE_ENV !== "admin"
208
+ ? { uid: "1" }
209
+ : user;
210
+ if (!uid ||
211
+ !table ||
212
+ !Object.keys(body)?.length ||
213
+ !client?.pk?.["crm.extra_data"] ||
214
+ !client?.pk?.["admin.custom_column"])
215
+ return null;
216
+ const loadTable = await getTemplate("table", table);
217
+ if (!client?.pk?.[loadTable?.table || table])
218
+ return null;
219
+ const pk = client?.pk?.[loadTable?.table || table];
220
+ const id = res[pk];
221
+ const { rows: properties = [] } = await client.query(`select column_id, name, title, format, data from admin.custom_column
222
+ where entity=$1 and uid=$2`, [table, uid]);
223
+ if (!id || !properties?.length || !client.pk?.["crm.extra_data"])
224
+ return null;
225
+ const q = `delete from crm.extra_data where property_entity='${table}' and object_id='${id}';${properties
226
+ .filter((el) => Object.keys(body).includes(el.name))
227
+ .map((el) => `insert into crm.extra_data(property_id,property_key,property_entity,object_id,${el.format?.toLowerCase() === "date" ? "value_date" : "value_text"})
228
+ select '${el.column_id}', '${el.name}', '${table}', '${id}', ${el.format?.toLowerCase() === "date"
229
+ ? `'${body[el.name]}'::timestamp without time zone`
230
+ : `'${body[el.name]}'::text`}`)
231
+ .join(";\n") || ""}`;
232
+ return client.query(q);
233
+ });
234
+ addHook("afterInsert", async ({ table, body, payload: res = {}, user = {}, }) => {
235
+ const { uid } = config?.auth?.disable || process.env.NODE_ENV !== "admin"
236
+ ? { uid: "1" }
237
+ : user;
238
+ if (!uid ||
239
+ !table ||
240
+ !Object.keys(body)?.length ||
241
+ !client?.pk?.["crm.extra_data"] ||
242
+ !client?.pk?.["admin.custom_column"])
243
+ return null;
244
+ const loadTable = await getTemplate("table", table);
245
+ if (!client?.pk?.[loadTable?.table || table])
246
+ return null;
247
+ const pk = client?.pk?.[loadTable?.table || table];
248
+ const id = res.rows?.[0]?.[pk];
249
+ const { rows: properties = [] } = await client.query(`select column_id, name, title, format, data from admin.custom_column
250
+ where entity=$1 and uid=$2`, [table, uid]);
251
+ if (!id || !properties?.length)
252
+ return null;
253
+ const q = properties
254
+ .filter((el) => Object.keys(body).includes(el.name))
255
+ .map((el) => `insert into crm.extra_data(property_id,property_key,property_entity,object_id,${el.format?.toLowerCase() === "date" ? "value_date" : "value_text"})
256
+ select '${el.column_id}', '${el.name}', '${table}', '${id}', ${el.format?.toLowerCase() === "date"
257
+ ? `'${body[el.name]}'::timestamp without time zone`
258
+ : `'${body[el.name]}'::text`}`)
259
+ .join(";\n");
260
+ return client.query(q);
261
+ });
43
262
  function plugin(fastify) {
44
263
  const opt = { prefix: "/api" };
45
- // fastify.register(import('@fastify/sensible'), {
46
- // errorHandler: false,
47
- // });
48
- // fastify.register(import('@fastify/url-data'), {
49
- // errorHandler: false,
50
- // });
51
264
  if (config.disableCors) {
52
265
  fastify.addHook("onSend", async (request, reply) => {
53
266
  reply.header("Access-Control-Allow-Origin", "*");
@@ -165,5 +378,172 @@ function plugin(fastify) {
165
378
  });
166
379
  }
167
380
  });
381
+ // from opengis/admin
382
+ const user1 = config?.auth?.disable || process.env.NODE_ENV !== "admin"
383
+ ? { uid: "1" }
384
+ : null;
385
+ fastify.addHook("onListen", async () => {
386
+ const json = await getMenu({ user: { uid: "1" }, pg: client }, null);
387
+ // insert interface list to db (user access management)
388
+ if (client?.pk?.["admin.routes"] && json?.menus?.length) {
389
+ const menuList = json?.menus?.filter?.((el) => el?.table ||
390
+ el?.component ||
391
+ el?.menu?.length /*&& el?.ua || el?.en || el?.name*/) || [];
392
+ // skip dupes
393
+ //admin_route_menu_id_fkey
394
+ menuList.forEach((el) => Object.assign(el, { ua: el?.ua || el?.en || el?.name }));
395
+ const uniqueList = menuList.filter((el, idx, arr) => el?.ua &&
396
+ arr.map((item) => item?.ua).indexOf(el?.ua) ===
397
+ idx);
398
+ const q = `insert into admin.menu(name, ord) values${uniqueList
399
+ .map((el, i) => `('${el?.ua.replace(/'/g, "''")}', ${i}) `)
400
+ .join(",")}
401
+ on conflict (name) do update set ord=excluded.ord, enabled=true returning name, menu_id`;
402
+ const { rows = [] } = uniqueList.length ? await client.query(q) : {};
403
+ const menus = rows.reduce((acc, curr) => Object.assign(acc, {
404
+ [curr.menu_id]: uniqueList.find((item) => item?.ua === curr.name),
405
+ }), {});
406
+ const values = Object.entries(menus).reduce((acc, curr) => {
407
+ if (curr[1]?.table || curr[1]?.component) {
408
+ acc.push({ ...curr[1], menuId: curr[0] });
409
+ }
410
+ curr[1]?.menu?.forEach?.((el) => acc.push({ ...el, menuId: curr[0] }));
411
+ return acc;
412
+ }, []);
413
+ await Promise.all(values
414
+ .filter((el) => el?.table)
415
+ .map(async (el) => {
416
+ const loadTable = await getTemplate("table", el.table);
417
+ Object.assign(el, {
418
+ table1: loadTable?.table || el.table,
419
+ actions: loadTable?.actions,
420
+ access: loadTable?.access,
421
+ });
422
+ }));
423
+ // console.log(values)
424
+ const q1 = `insert into admin.routes(route_id, alias, title, menu_id, table_name, actions, access, query)
425
+ values ${values
426
+ .filter((el) => el?.path)
427
+ .map((el) => `('${el.path}', ${el.table ? `'${el.table}'` : null},
428
+ ${el.title || el.ua
429
+ ? `'${(el.title || el.ua).replace(/'/g, "''")}'`
430
+ : null},
431
+ ${el.menuId ? `'${el.menuId}'` : null}, ${el.table1 ? `'${el.table1}'` : null},
432
+ ${el.actions?.length ? `'{ ${el.actions} }'::text[]` : null}, ${el.access ? `'${el.access}'` : null},
433
+ ${el.query ? `'${el.query.replace(/'/g, "''")}'` : "'1=1'"})`)
434
+ .join(",")}
435
+ on conflict (route_id) do update set menu_id=excluded.menu_id, alias=excluded.alias, title=excluded.title, enabled=true, query=excluded.query,
436
+ table_name=excluded.table_name, actions=excluded.actions, access=excluded.access returning route_id, table_name`;
437
+ try {
438
+ console.log("admin/hook routes sql start");
439
+ const { rowCount: menuCount } = await client.query(`delete from admin.menu
440
+ where not array[menu_id] <@ $1::text[] and menu_id not in (select menu_id from admin.routes)`, [rows.map((el) => el.menu_id)]);
441
+ console.log("delete deprecated menus ok", menuCount);
442
+ const { rowCount: interfaceCount } = await client.query(`delete from admin.routes
443
+ where not array[route_id] <@ $1::text[] and route_id not in (select route_id from admin.role_access)`, [values.filter((el) => el?.path)]);
444
+ console.log("delete deprecated interfaces ok", interfaceCount);
445
+ const { rowCount } = values?.length ? await client.query(q1) : {};
446
+ console.log("insert interfaces ok", rowCount);
447
+ }
448
+ catch (err) {
449
+ console.log("admin/hook routes sql error", values, q1, err);
450
+ }
451
+ }
452
+ });
453
+ fastify.addHook("onListen", async () => {
454
+ const clsQuery = [];
455
+ if (!client?.pk?.["admin.cls"])
456
+ return;
457
+ const selectList = getTemplatePath("select");
458
+ const clsList = getTemplatePath("cls")?.filter((el) => !(selectList?.map((el) => el?.[0]) || []).includes(el[0]));
459
+ const cls = (selectList || []).concat(clsList || [])?.map((el) => ({
460
+ name: el[0],
461
+ module: path.basename(path.dirname(path.dirname(el[1]))),
462
+ type: { json: "cls", sql: "select" }[el[2]],
463
+ }));
464
+ if (!cls?.length)
465
+ return;
466
+ try {
467
+ const hashes = await rclient
468
+ .hgetall("cls-insert-hashes")
469
+ .then((obj) => Object.keys(obj));
470
+ const dbdata = await client
471
+ .query(`select json_object_agg(name, hash) from admin.cls where parent is null`)
472
+ .then((el) => el.rows?.[0]?.json_object_agg || {});
473
+ const names = Object.keys(dbdata);
474
+ console.log("admin/hook cls promise start");
475
+ const qHashes = await Promise.all(cls
476
+ .filter((el, idx, arr) => arr.map((item) => item.name).indexOf(el.name) === idx)
477
+ .map(async (el) => {
478
+ const { name, module, type } = el;
479
+ const loadTemplate = await getTemplate(type, name);
480
+ el.hash = createHash("md5")
481
+ .update(type === "cls"
482
+ ? JSON.stringify(loadTemplate)
483
+ : loadTemplate?.sql || loadTemplate)
484
+ .digest("hex");
485
+ el.dbhash = dbdata[name];
486
+ // check for changes by redis hash / dropped from db / changed at git project
487
+ el.update =
488
+ !hashes.includes(el.hash) ||
489
+ !names.includes(name) ||
490
+ el.hash !== el.dbhash;
491
+ if (type === "select" &&
492
+ (loadTemplate?.sql || loadTemplate) &&
493
+ el.update) {
494
+ clsQuery.push(`insert into admin.cls(name,type,data,module,hash) values('${name}','sql','${(loadTemplate?.sql || loadTemplate)?.replace(/'/g, "''")}', '${module?.replace(/'/g, "''")}','${el.hash}')`);
495
+ if (config.trace)
496
+ console.log(name, type, "insert fresh select");
497
+ return el.hash;
498
+ }
499
+ else if (type === "cls" && loadTemplate?.length && el.update) {
500
+ clsQuery.push(`insert into admin.cls(name,type, module,hash) values('${name}','json', '${module?.replace(/'/g, "''")}','${el.hash}');
501
+ insert into admin.cls(code,name,parent,icon,data)
502
+ select value->>'id',value->>'text','${name}',value->>'icon',value->>'data'
503
+ from json_array_elements('${JSON.stringify(loadTemplate).replace(/'/g, "''")}'::json)`);
504
+ if (config.trace)
505
+ console.log(name, type, "insert fresh cls");
506
+ return el.hash;
507
+ }
508
+ else if (hashes.includes(el.hash)) {
509
+ if (config.trace)
510
+ console.log(name, type, names.includes(name)
511
+ ? "skip equal hash"
512
+ : "insert missing cls");
513
+ return el.hash;
514
+ }
515
+ else {
516
+ if (config.trace)
517
+ console.log(name, type, "empty");
518
+ return el.hash;
519
+ }
520
+ }));
521
+ // debug
522
+ const logDir = path.join(cwd, "log/migration");
523
+ mkdirSync(logDir, { recursive: true });
524
+ writeFileSync(path.join(logDir, `${path.basename(cwd)}-${client.options?.database}-cls.sql`), clsQuery.filter(Boolean).join(";"));
525
+ writeFileSync(path.join(logDir, `${path.basename(cwd)}-${client.options?.database}-cls.json`), JSON.stringify(cls));
526
+ const { rowCount = 0 } = await client.query("delete from admin.cls where $1::text[] && array[name,parent]", [
527
+ cls
528
+ .filter((el) => el.update)
529
+ .map((el) => el.name),
530
+ ]);
531
+ console.log("fastify-table/hook old cls deleted", rowCount);
532
+ if (clsQuery.filter((el) => el).length) {
533
+ console.log("fastify-table cls sql start", clsQuery?.length);
534
+ await client.query(clsQuery.filter((el) => el).join(";"));
535
+ await Promise.all(qHashes
536
+ .filter(Boolean)
537
+ .map(async (el) => rclient.hset("cls-insert-hashes", el, 1)));
538
+ logger.file("migration/hash", { list: qHashes.filter(Boolean) });
539
+ console.log("fastify-table/hook cls sql finish", clsQuery?.length);
540
+ }
541
+ console.log("fastify-table/hook cls promise finish", rowCount);
542
+ }
543
+ catch (err) {
544
+ console.error("fastify-table/hook cls sql error", err.toString());
545
+ console.trace(err);
546
+ }
547
+ });
168
548
  }
169
549
  export default fp(plugin);
@@ -65,6 +65,7 @@ function plugin(fastify) {
65
65
  };
66
66
  req.user = req.session.passport.user;
67
67
  }
68
+ // ! intentional: null || undefined > undefined
68
69
  req.user = req.user || req.session?.passport?.user || undefined; // fix for user.uid errors, by default user is null, while with express passport it was {}, unauthorized user does not trigger serializer
69
70
  if (config.trace && false) {
70
71
  console.log("req.user?.uid", req.user?.uid, "req.session?.passport?.user?.uid", req.session?.passport?.user?.uid, "config.auth", config.auth);
@@ -9,7 +9,7 @@ import yml2json from "../../yml/funcs/yml2json.js";
9
9
  const cwd = process.cwd();
10
10
  export default function getTemplatePath(type) {
11
11
  if (!type)
12
- return null;
12
+ return [];
13
13
  // form cache
14
14
  if (loadTemplatePath[type])
15
15
  return loadTemplatePath[type];
@@ -1,15 +1,14 @@
1
1
  import fs from "node:fs";
2
- import path from "node:path";
3
- import config from "../../../../config.js";
4
- const loadTemplate = {};
5
- export default async function getTemplateDir(type) {
2
+ import getTemplatePath from "./getTemplatePath.js";
3
+ import loadTemplate from "./loadTemplate.js";
4
+ export default function getTemplateDir(type) {
6
5
  if (!type)
7
6
  return null;
8
- const cwd = process.cwd();
9
- const typeDir = path.join(cwd, config.templateDir || "server/templates", type);
7
+ const arr = Array.isArray(type)
8
+ ? type.map((el) => getTemplatePath(el)).flat()
9
+ : getTemplatePath(type);
10
10
  if (!loadTemplate[type]) {
11
- const typeList = fs.existsSync(typeDir) ? fs.readdirSync(typeDir) : [];
12
- loadTemplate[type] = typeList;
11
+ loadTemplate[type] = arr.filter((el) => fs.existsSync(el[1]));
13
12
  }
14
13
  return loadTemplate[type];
15
14
  }
@@ -31,7 +31,7 @@ async function readMenu() {
31
31
  }
32
32
  export default async function adminMenu({ user = {}, session, pg = pgClients.client, }, reply) {
33
33
  const time = Date.now();
34
- if (!user.uid && !config.auth?.disable) {
34
+ if (!user.uid && !config.auth?.disable && reply) {
35
35
  return reply.status(403).send("access restricted");
36
36
  }
37
37
  const menus = isProduction && menuCache.length ? menuCache : await readMenu();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [