@opengis/cms 0.0.16 → 0.0.18
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/README.md +96 -3
- package/dist/assets/AddUser-CX-McfRW.js +1 -0
- package/dist/assets/ApiKeys-DSv1exYv.js +16 -0
- package/dist/assets/Appearance-DDtOUvCV.js +6 -0
- package/dist/assets/ArticlesPage-D6B3cZsl.js +6 -0
- package/dist/assets/BuilderPage-CCeSMVWe.js +1 -0
- package/dist/assets/CollectionsBreadcrumb.vue_vue_type_script_setup_true_lang-DVYVfYF4.js +1 -0
- package/dist/assets/CollectionsPage-CHk8Cn5k.js +1 -0
- package/dist/assets/Dashboard-Bs7sXO6h.js +11 -0
- package/dist/assets/EditCollectionPage-gPuLJrN8.js +41 -0
- package/dist/assets/EmailPage-DhlWsPxk.js +1 -0
- package/dist/assets/EmptyData-Ct-xQv_N.js +1 -0
- package/dist/assets/FeedbackPage-DtaOncVv.js +1 -0
- package/dist/assets/Logs-CZ5klHNK.js +1 -0
- package/dist/assets/MediaBreadcrumb-BpOxt5PK.js +11 -0
- package/dist/assets/MediaPage-DqRcZFlO.js +16 -0
- package/dist/assets/MenuAddPage-BLcoVgrS.js +1 -0
- package/dist/assets/MenuItemPage-B2otXqkz.js +20 -0
- package/dist/assets/MenuPage-C00m4Fc_.js +1 -0
- package/dist/assets/MonacoEditor.vue_vue_type_script_setup_true_lang-CjoEsC67.js +3 -0
- package/dist/assets/PermissionsPage-LtqcCJ14.js +1 -0
- package/dist/assets/Settings-BHH6RoBP.js +1 -0
- package/dist/assets/SettingsTable-CY5pZx1z.js +1 -0
- package/dist/assets/SettingsTitle-t4WJBFxZ.js +1 -0
- package/dist/assets/SingletonsPage-Bn2Ypjhs.js +6 -0
- package/dist/assets/TagsPage-DAiakEth.js +1 -0
- package/dist/assets/UniversalTable.vue_vue_type_script_setup_true_lang-BQ5m4aZd.js +11 -0
- package/dist/assets/UniversalTablePagination.vue_vue_type_script_setup_true_lang-DppVBws0.js +1 -0
- package/dist/assets/Users-CMH5j0db.js +1 -0
- package/dist/assets/UsersPage-CDGreEib.js +1 -0
- package/dist/assets/arrow-up-DCe0WsrM.js +16 -0
- package/dist/assets/{calendar-hsWc4yH-.js → calendar-o9t4MkD2.js} +1 -1
- package/dist/assets/chevron-left-WFftVS9c.js +6 -0
- package/dist/assets/chevron-right-BiiSb3Be.js +6 -0
- package/dist/assets/contentForm-unZQhjCu.js +6 -0
- package/dist/assets/en-BDx3Svx8.js +1 -0
- package/dist/assets/eye-Dijywc6g.js +6 -0
- package/dist/assets/file-B_duymIT.js +6 -0
- package/dist/assets/general-CkN_0qIV.js +1 -0
- package/dist/assets/index-BIp7eSXk.js +1 -0
- package/dist/assets/index-DCW2e4Az.js +9 -0
- package/dist/assets/index-DGweaj24.js +1 -0
- package/dist/assets/index-W-qQIppj-BDlsxaGB.js +1 -0
- package/dist/assets/index-W-qQIppj-BsopI3Hz-BIZR-dhy.js +1 -0
- package/dist/assets/index-oQz9FOqL.css +1 -0
- package/dist/assets/index-yMJAVBXk.js +290 -0
- package/dist/assets/list-CXRbSNky.js +6 -0
- package/dist/assets/pencil-CwnPP4IJ.js +6 -0
- package/dist/assets/{plus-D9etvrM2.js → plus-DLR44m6p.js} +1 -1
- package/dist/assets/save-FeDrOUOd.js +6 -0
- package/dist/assets/{search-BI-hqhq6.js → search-C4-fHihx.js} +1 -1
- package/dist/assets/{square-pen-61CkyXzK.js → square-pen-xVs4e8Yb.js} +1 -1
- package/dist/assets/{trash-2-CJSl_r88.js → trash-2-BGXMNU3d.js} +1 -1
- package/dist/assets/uk-BA7DIKEL.js +1 -0
- package/dist/assets/useDebounce-DFq3rxAW.js +1 -0
- package/dist/assets/vs-form-reletion-link-C-xrdHDl.js +20 -0
- package/dist/assets/vs-form-reletion-link-bk-9ZkDH.css +1 -0
- package/dist/assets/vue.-sixQ7xP-CUPNuJcq.js +1 -0
- package/dist/assets/vuedraggable.umd-W_2WTF6i.js +14 -0
- package/dist/assets/{x-BNquQe5y.js → x-D2t-wfBe.js} +1 -1
- package/dist/index.html +14 -9
- package/module/cms/card/cms.content.table/index.yml +17 -0
- package/module/cms/card/cms.content.table/main_info.hbs +26 -0
- package/module/cms/card/cms.menu.table/content_info.hbs +16 -0
- package/module/cms/card/cms.menu.table/index.yml +18 -0
- package/module/cms/card/cms.menu.table/main_info.hbs +22 -0
- package/module/cms/card/cms.settings.table/index.yml +13 -0
- package/module/cms/card/cms.settings.table/main_info.hbs +20 -0
- package/module/cms/cls/content.status.json +18 -0
- package/module/cms/cls/user_type.json +10 -0
- package/module/cms/form/admin.users.form.json +78 -0
- package/module/cms/form/cms.content.form.json +79 -0
- package/module/cms/form/cms.menu.form.json +69 -0
- package/module/cms/form/cms.settings.form.json +32 -0
- package/module/cms/menu.json +24 -0
- package/module/cms/router.js +154 -0
- package/module/cms/select/cms.page_type.sql +2 -0
- package/module/cms/select/collection.sql +1 -0
- package/module/cms/select/locale.sql +17 -0
- package/module/cms/select/news_tag_id.sql +12 -0
- package/module/cms/select/tag_id.sql +1 -0
- package/module/cms/table/admin.users.table.json +54 -0
- package/module/cms/table/cms.content.table.json +106 -0
- package/module/cms/table/cms.menu.table.json +73 -0
- package/module/cms/table/cms.settings.table.json +57 -0
- package/module/cms/table/collection.default.table.json +102 -0
- package/module/cms/table/single.default.table.json +115 -0
- package/package.json +36 -31
- package/plugin.js +63 -23
- package/server/app.js +20 -3
- package/server/functions/getDraftKey.js +22 -0
- package/server/index.js +2 -3
- package/server/migrations/fixes.sql +124 -0
- package/server/migrations/site.sql +338 -249
- package/server/plugins/adminHook.js +2 -2
- package/server/plugins/hook.js +53 -61
- package/server/plugins/vite.js +5 -5
- package/server/routes/cms/controllers/cmsStat.js +56 -0
- package/server/routes/cms/controllers/cmsSuggest.js +58 -0
- package/server/routes/cms/controllers/deleteContent.js +114 -59
- package/server/routes/cms/controllers/deleteMedia.js +75 -46
- package/server/routes/cms/controllers/downloadMedia.js +48 -48
- package/server/routes/cms/controllers/getContent.js +110 -95
- package/server/routes/cms/controllers/getContentBySlug.js +95 -0
- package/server/routes/cms/controllers/getPermissions.js +15 -15
- package/server/routes/cms/controllers/insertContent.js +218 -68
- package/server/routes/cms/controllers/listMedia.js +93 -72
- package/server/routes/cms/controllers/metadataMedia.js +38 -37
- package/server/routes/cms/controllers/properties.get.js +53 -0
- package/server/routes/cms/controllers/properties.post.js +99 -0
- package/server/routes/cms/controllers/searchContent.js +205 -0
- package/server/routes/cms/controllers/setPermissions.js +49 -49
- package/server/routes/cms/controllers/translate.js +90 -0
- package/server/routes/cms/controllers/updateContent.js +238 -111
- package/server/routes/cms/controllers/uploadMedia.js +78 -65
- package/server/routes/cms/index.mjs +81 -12
- package/server/routes/cms/utils/additionalData.js +36 -0
- package/server/routes/cms/utils/getCollection.js +82 -0
- package/server/routes/cms/utils/getSingle.js +188 -0
- package/server/routes/cms/utils/insertContentLocalization.js +87 -0
- package/server/routes/cms/utils/requestTranslation.js +85 -0
- package/server/routes/cms/utils/updateLocalization.js +48 -0
- package/server/routes/cmsSpace/controllers/deleteSpace.js +26 -0
- package/server/routes/cmsSpace/controllers/getSpaces.js +28 -0
- package/server/routes/cmsSpace/controllers/insertSpace.js +22 -0
- package/server/routes/cmsSpace/controllers/updateSpace.js +24 -0
- package/server/routes/cmsSpace/index.mjs +20 -0
- package/server/routes/contentType/controllers/addContentType.js +162 -0
- package/server/routes/contentType/controllers/contentTypeList.js +54 -0
- package/server/routes/contentType/controllers/delContentType.js +75 -0
- package/server/routes/contentType/controllers/editContentType.js +61 -0
- package/server/routes/contentType/controllers/getContentType.js +37 -0
- package/server/routes/contentType/index.mjs +29 -19
- package/server/routes/contentType/utils/updateContents.js +29 -0
- package/server/routes/contentType/utils/updateCustomContentTable.js +56 -0
- package/server/routes/feedback/controllers/email.list.js +25 -0
- package/server/routes/feedback/controllers/feedback.js +49 -0
- package/server/routes/feedback/controllers/feedback.list.js +38 -0
- package/server/routes/feedback/controllers/news.subscriptions.js +44 -0
- package/server/routes/feedback/index.mjs +72 -0
- package/server/routes/logs/controllers/export.user.logs.js +78 -0
- package/server/routes/logs/controllers/user.logs.js +45 -0
- package/server/routes/logs/index.mjs +9 -0
- package/server/routes/menu/controllers/addMenu.js +38 -0
- package/server/routes/menu/controllers/delMenu.js +32 -0
- package/server/routes/menu/controllers/editMenu.js +42 -0
- package/server/routes/menu/controllers/getMenu.js +43 -0
- package/server/routes/menu/index.mjs +13 -0
- package/server/routes/migration/controllers/collectionToCustom.js +137 -0
- package/server/routes/migration/index.mjs +8 -0
- package/server/routes/tags/controllers/add.tags.js +25 -0
- package/server/routes/tags/controllers/del.tags.js +20 -0
- package/server/routes/tags/controllers/edit.tags.js +26 -0
- package/server/routes/tags/controllers/get.tags.js +16 -0
- package/server/routes/tags/index.mjs +14 -0
- package/server/templates/page/login.html +73 -5
- package/server/templates/select/core.user_mentioned.sql +2 -0
- package/src/index.ts +122 -0
- package/dist/assets/ArticlesPage-BveM4q3g.js +0 -11
- package/dist/assets/CollectionsPage-D5td-UBm.js +0 -1
- package/dist/assets/ContentBlock.vue_vue_type_script_setup_true_lang-BwF6D-yB.js +0 -30
- package/dist/assets/CreateCollectionPage-Cu0RW5ui.js +0 -76
- package/dist/assets/Dashboard-faSjwmB8.js +0 -11
- package/dist/assets/EditCollectionPage-K5oPPzCd.js +0 -1
- package/dist/assets/MediaPage-BoW3aWgN.js +0 -1
- package/dist/assets/PermissionsPage-DGy5fha2.js +0 -1
- package/dist/assets/SingletonsPage-C1X2xkQE.js +0 -1
- package/dist/assets/UniversalTable.vue_vue_type_script_setup_true_lang-DUqfWJcy.js +0 -6
- package/dist/assets/contentForm-DMVC4vho.js +0 -1
- package/dist/assets/database-BTxZQzYy.js +0 -6
- package/dist/assets/index-9GY17iSP.css +0 -1
- package/dist/assets/index-DYyZmLWO.js +0 -2138
- package/dist/assets/index-xsH4HHeE.js +0 -6
- package/dist/assets/save-C2B6th9J.js +0 -11
- package/dist/assets/settings-DbyDiH2g.js +0 -6
- package/dist/assets/vue.-sixQ7xP-DwXf3zRn.js +0 -1
- package/dist/assets/x-circle-C3q70RMH.js +0 -16
- package/server/routes/contentType/controllers/cms.type.delete.js +0 -22
- package/server/routes/contentType/controllers/cms.type.get.js +0 -22
- package/server/routes/contentType/controllers/cms.type.list.js +0 -25
- package/server/routes/contentType/controllers/cms.type.post.js +0 -22
- package/server/routes/contentType/controllers/cms.type.put.js +0 -24
- package/server/routes/contentType/utils/builderCache.js +0 -58
- package/server/routes/fileContent/data/deleteContent.js +0 -34
- package/server/routes/fileContent/data/deleteMedia.js +0 -28
- package/server/routes/fileContent/data/downloadMedia.js +0 -41
- package/server/routes/fileContent/data/getContent.js +0 -32
- package/server/routes/fileContent/data/insertContent.js +0 -37
- package/server/routes/fileContent/data/listMedia.js +0 -47
- package/server/routes/fileContent/data/metadataMedia.js +0 -38
- package/server/routes/fileContent/data/updateContent.js +0 -40
- package/server/routes/fileContent/data/uploadMedia.js +0 -49
- package/server/routes/fileContent/index.mjs +0 -54
- package/server/routes/fileContent/type/contentTypeList.js +0 -7
- package/server/routes/fileContent/type/createContentType.js +0 -31
- package/server/routes/fileContent/type/deleteContentType.js +0 -29
- package/server/routes/fileContent/type/getContentType.js +0 -15
- package/server/routes/fileContent/type/updateContentType.js +0 -40
- package/server/routes/fileContent/utils/astroBuilderCache.js +0 -47
- package/server/routes/fileContent/utils/contentDir.js +0 -12
- package/server/routes/fileContent/utils/contentTypeExists.js +0 -15
|
@@ -4,9 +4,9 @@ import { createReadStream } from 'node:fs';
|
|
|
4
4
|
async function plugin(fastify) {
|
|
5
5
|
// preSerialization
|
|
6
6
|
fastify.addHook('preSerialization', async (req, reply, payload) => {
|
|
7
|
-
if (req.url.includes('/suggest/') && !req.query.json) {
|
|
7
|
+
/*if (req.url.includes('/suggest/') && !req.query.json) {
|
|
8
8
|
return payload?.data;
|
|
9
|
-
}
|
|
9
|
+
}*/
|
|
10
10
|
/* if (payload.redirect) {
|
|
11
11
|
return reply.redirect(payload.redirect);
|
|
12
12
|
}*/
|
package/server/plugins/hook.js
CHANGED
|
@@ -1,67 +1,59 @@
|
|
|
1
1
|
import fp from 'fastify-plugin';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
defaultColumns.forEach(el => columnList.push(el));
|
|
44
|
-
|
|
45
|
-
return `CREATE TABLE IF NOT EXISTS site.${name} ();
|
|
46
|
-
ALTER TABLE site.${name} ADD COLUMN IF NOT EXISTS ${name}_id text not null default next_id() PRIMARY KEY;
|
|
47
|
-
ALTER TABLE site.${name} ADD COLUMN IF NOT EXISTS ${slug} text;
|
|
48
|
-
${columnList.map(el => `ALTER TABLE site.${name} ADD COLUMN IF NOT EXISTS ${el.name} ${matches[el.type] || 'text'};`).join('\n')}
|
|
49
|
-
${columnList.filter(el => el.default).map(el => `ALTER TABLE site.${name} ALTER COLUMN ${el.name} SET DEFAULT ${el.default};`).join('\n')}
|
|
50
|
-
${columnList.filter(el => Object.hasOwn(el, 'required')).map(el => `ALTER TABLE site.${name} ALTER COLUMN ${el.name} ${el.required ? 'SET' : 'DROP'} NOT NULL;`).join('\n')}
|
|
51
|
-
${columnList.filter(el => el.label).map(el => `COMMENT ON COLUMN site.${name}.${el.name} IS '${el.label}';`).join('\n')}`;
|
|
2
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
import { addHook, addCron, config, pgClients } from '@opengis/fastify-table/utils.js';
|
|
5
|
+
|
|
6
|
+
import getDraftKey from '../functions/getDraftKey.js';
|
|
7
|
+
|
|
8
|
+
const pg = pgClients.client;
|
|
9
|
+
|
|
10
|
+
const q = `select
|
|
11
|
+
content_id as id, a.status, type, published_at, table_name as table
|
|
12
|
+
from site.contents a
|
|
13
|
+
left join site.content_types b
|
|
14
|
+
on a.content_type_id=b.content_type_id
|
|
15
|
+
where
|
|
16
|
+
b.visible
|
|
17
|
+
and case
|
|
18
|
+
when type = 'single'
|
|
19
|
+
then
|
|
20
|
+
a.status='delayPublished'
|
|
21
|
+
and CURRENT_DATE >= published_at::date
|
|
22
|
+
else true
|
|
23
|
+
end`;
|
|
24
|
+
|
|
25
|
+
async function updateDelayPublished() {
|
|
26
|
+
const rows = await pg.query(q).then(el => el.rows || []);
|
|
27
|
+
|
|
28
|
+
const result = await Promise.all(rows.map(async ({ id, status, type, table }) => {
|
|
29
|
+
if (status === 'delayPublished' && type === 'single') {
|
|
30
|
+
const { rowCount } = await pg.query(`update site.contents set status='published' where content_id=$1`, [id]);
|
|
31
|
+
return { id, type, rowCount };
|
|
32
|
+
} else if (table && type === 'collection') {
|
|
33
|
+
const { rowCount } = await pg.query(`update data."${table}" set status='published' where status='delayPublished' and now() >= published_at`);
|
|
34
|
+
return { id, type, rowCount };
|
|
35
|
+
} else {
|
|
36
|
+
return { id, type, skip: true };
|
|
37
|
+
}
|
|
38
|
+
}));
|
|
39
|
+
const r = { updated: result.reduce((acc, curr) => acc + +(curr.rowCount || 0), 0), skipped: result.filter(el => el.skip).length };
|
|
40
|
+
if (config.trace) console.log(r);
|
|
41
|
+
return r;
|
|
52
42
|
}
|
|
53
|
-
|
|
54
43
|
async function plugin(app) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
44
|
+
addHook('afterUser', async ({ payload = {} }) => {
|
|
45
|
+
if (payload?.user) {
|
|
46
|
+
// get from cache, refresh hourly
|
|
47
|
+
const { draftKey, ttl } = await getDraftKey();
|
|
48
|
+
Object.assign(payload.user, { draftKey, draftKeyTTL: ttl });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
// force refresh at restart
|
|
52
|
+
app.addHook('onListen', async () => {
|
|
53
|
+
await getDraftKey(true);
|
|
54
|
+
});
|
|
55
|
+
addCron(updateDelayPublished, 60 * 60);
|
|
56
|
+
updateDelayPublished();
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
export default fp(plugin);
|
package/server/plugins/vite.js
CHANGED
|
@@ -7,7 +7,7 @@ import { config } from '@opengis/fastify-table/utils.js';
|
|
|
7
7
|
const dir = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const root = `${dir}/../..`;
|
|
9
9
|
const isProduction = process.env.NODE_ENV === 'production' || config.production;
|
|
10
|
-
|
|
10
|
+
console.log({ isProduction })
|
|
11
11
|
async function plugin(fastify) {
|
|
12
12
|
// vite server
|
|
13
13
|
if (!isProduction) {
|
|
@@ -33,7 +33,7 @@ async function plugin(fastify) {
|
|
|
33
33
|
// this is middleware for vite's dev servert
|
|
34
34
|
fastify.addHook('onRequest', async (req, reply) => {
|
|
35
35
|
const { user } = req.session?.passport || {};
|
|
36
|
-
if (!user && config.pg && !config.auth?.disable) {
|
|
36
|
+
if (!user && config.pg && !req.url.startsWith('/src/') && !config.auth?.disable) {
|
|
37
37
|
return reply.redirect('/login');
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -50,9 +50,9 @@ async function plugin(fastify) {
|
|
|
50
50
|
fastify.get('*', async (req, reply) => {
|
|
51
51
|
const { user } = req.session?.passport || {};
|
|
52
52
|
const indexPath = path.join(root, 'dist', 'index.html');
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if (!user && config.pg && !req.url.startsWith('/src/') && !config.auth?.disable) {
|
|
54
|
+
return reply.redirect('/login');
|
|
55
|
+
}
|
|
56
56
|
if (!fs.existsSync(indexPath)) {
|
|
57
57
|
return reply.status(404).send('index.html not found');
|
|
58
58
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { metaFormat, pgClients } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
const q = `select
|
|
4
|
+
count(distinct object_id) as cdtotal,
|
|
5
|
+
count(distinct object_id) filter( where date_trunc('month', created_at) = date_trunc('month', CURRENT_DATE) ) as cdnewcount from site.content_data`;
|
|
6
|
+
|
|
7
|
+
const q1 = `select
|
|
8
|
+
count(*) as usercount,
|
|
9
|
+
count(*) filter( where date_trunc('month', cdate) = date_trunc('month', CURRENT_DATE) ) as usernewcount from admin.users`;
|
|
10
|
+
|
|
11
|
+
const q2 = `select
|
|
12
|
+
count(*) as mediacount,
|
|
13
|
+
count(*) filter( where date_trunc('month', created_at) = date_trunc('month', CURRENT_DATE) ) as medianewcount from site.media`;
|
|
14
|
+
|
|
15
|
+
const q3 = `select type, title, updated_at, content_type_id as object_id, slug as content_id from site.contents a
|
|
16
|
+
left join lateral (select type from site.content_types where content_type_id=a.content_type_id limit 1)b on 1=1
|
|
17
|
+
where 1=1 order by updated_at desc limit 5`;
|
|
18
|
+
|
|
19
|
+
export default async function cmsStat({ pg = pgClients.client }, reply) {
|
|
20
|
+
if (!pg) {
|
|
21
|
+
return reply.status(500).send('empty pg');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { cdtotal = 0, cdnewcount = 0 } = await pg.query(q).then(el => el.rows?.[0] || {});
|
|
25
|
+
|
|
26
|
+
const customTables = await pg.query(`select array_agg(table_name) from site.content_types where type='collection' and table_name is not null`).then(el => el.rows?.[0]?.array_agg || []);
|
|
27
|
+
|
|
28
|
+
const custom = await Promise.all(customTables.filter(table => pg?.pk?.[`data.${table}`]).map(async table => {
|
|
29
|
+
// await pg.query(`alter table data.${table} add column if not exists created_at timestamp with time zone not null default now()`);
|
|
30
|
+
const { total = 0, newcount = 0 } = await pg.queryCache(`select count(*) as total, count(*) filter( where date_trunc('month', created_at) = date_trunc('month', CURRENT_DATE) ) as newcount from data.${table}`, { table: `data.${table}` }).then(el => el.rows?.[0] || {});
|
|
31
|
+
return { table, total, newcount };
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const totalContent = custom.reduce((acc, el) => acc + +el.total, +cdtotal);
|
|
35
|
+
const newContent = custom.reduce((acc, el) => acc + +el.newcount, +cdnewcount);
|
|
36
|
+
|
|
37
|
+
const { usercount = 0, usernewcount = 0 } = await pg.query(q1).then(el => el.rows?.[0] || {});
|
|
38
|
+
|
|
39
|
+
const { mediacount = 0, medianewcount = 0 } = await pg.query(q2).then(el => el.rows?.[0] || {});
|
|
40
|
+
|
|
41
|
+
const { rows = [] } = await pg.query(q3);
|
|
42
|
+
|
|
43
|
+
await metaFormat({ rows, cls: { updated_by: 'core.user_mentioned' }, sufix: false }, pg);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
stat: {
|
|
47
|
+
totalContent,
|
|
48
|
+
newContent,
|
|
49
|
+
totalUsers: +usercount,
|
|
50
|
+
newUsers: +usernewcount,
|
|
51
|
+
totalMediaFiles: +mediacount,
|
|
52
|
+
newMediaFiles: +medianewcount,
|
|
53
|
+
},
|
|
54
|
+
recentContent: rows,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { config, pgClients } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
const maxLimit = 50;
|
|
4
|
+
|
|
5
|
+
export default async function suggest({ pg = pgClients.client, params = {}, query = {}, user = {} }, reply) {
|
|
6
|
+
const t1 = Date.now();
|
|
7
|
+
|
|
8
|
+
const { id } = params;
|
|
9
|
+
const { key, val } = query;
|
|
10
|
+
|
|
11
|
+
if (!pg?.pk) {
|
|
12
|
+
return reply.status(500).send('empty pg');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!id) {
|
|
16
|
+
return reply.status(400).send('not enough params');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const limit = Math.min(query.limit || 16, maxLimit);
|
|
20
|
+
const offset = query.page && query.page > 0 ? (query.page - 1) * limit : 0;
|
|
21
|
+
|
|
22
|
+
const { ctid, table } = await pg.query(
|
|
23
|
+
'select content_type_id as ctid, table_name as table, columns from site.content_types where $1 in (content_type_id, name)',
|
|
24
|
+
[id],
|
|
25
|
+
).then(el => el.rows?.[0] || {});
|
|
26
|
+
|
|
27
|
+
if (!ctid) {
|
|
28
|
+
return reply.status(404).send('content type not found');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// site.content_data
|
|
32
|
+
if (!table) {
|
|
33
|
+
const q = `select content_id as id, title as text from site.contents where content_type_id=$1 and ${key ? `lower(title) ~ lower($2)` : (val ? 'content_id=$2' : '1=1')} limit ${limit}`;
|
|
34
|
+
|
|
35
|
+
const { rows = [] } = await pg.query(q, [ctid, (key || val)].filter(Boolean));
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
time: Date.now() - t1,
|
|
39
|
+
limit,
|
|
40
|
+
count: rows.length,
|
|
41
|
+
mode: 'array',
|
|
42
|
+
sql: (config.local || user?.user_type?.includes?.('admin')) ? q : undefined,
|
|
43
|
+
data: rows,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const q = `select id, title as text from data.${table} where ${key ? `lower(title) ~ lower($1)` : (val ? 'id=$1' : '1=1')} limit ${limit}`;
|
|
48
|
+
const { rows = [] } = await pg.query(q, [key || val].filter(Boolean));
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
time: Date.now() - t1,
|
|
52
|
+
limit,
|
|
53
|
+
count: rows.length,
|
|
54
|
+
mode: 'array',
|
|
55
|
+
sql: (config.local || user?.user_type?.includes?.('admin')) ? q : undefined,
|
|
56
|
+
data: rows,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1,60 +1,115 @@
|
|
|
1
|
-
import { config, pgClients, dataDelete } from '@opengis/fastify-table/utils.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
1
|
+
import { config, pgClients, dataUpdate, dataDelete } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
export default async function deleteContent(req, reply) {
|
|
4
|
+
const {
|
|
5
|
+
pg = pgClients.client,
|
|
6
|
+
params = {},
|
|
7
|
+
user = {},
|
|
8
|
+
headers = {},
|
|
9
|
+
} = req;
|
|
10
|
+
|
|
11
|
+
const { type, id } = params;
|
|
12
|
+
|
|
13
|
+
if (!type) {
|
|
14
|
+
return reply.status(400).send('not enough params: type');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!id) {
|
|
18
|
+
return reply.status(400).send('not enough params: id');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if (id === 'pages') {
|
|
23
|
+
return reply.status(403).send('access restricted: pages contents cannot be deleted');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const arr = config.pg ? await pg.query(`select array_agg(relname)::text[] from pg_class a
|
|
27
|
+
left join pg_namespace b on a.relnamespace=b.oid
|
|
28
|
+
where a.relam=2 and b.nspname='data'`).then(el => el.rows?.[0]?.array_agg || []) : [];
|
|
29
|
+
|
|
30
|
+
if (!arr.length) {
|
|
31
|
+
return reply.status(400).send('empty schema: data');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const table = arr.find(el => el === params.type);
|
|
35
|
+
|
|
36
|
+
const { ctid, dbtable } = await pg.query('select content_type_id as ctid, table_name as dbtable from site.content_types where $1 in (content_type_id, name)', [params.type])
|
|
37
|
+
.then(el => el.rows?.[0] || {});
|
|
38
|
+
|
|
39
|
+
// singletone with extra at site.content_data
|
|
40
|
+
if (!table && !dbtable && ctid) {
|
|
41
|
+
const { cid, status } = ctid === 'pages' && id
|
|
42
|
+
? await pg.query(
|
|
43
|
+
'select content_id as cid, status from site.contents where content_id=$1 limit 1',
|
|
44
|
+
[id]).then(el => el.rows?.[0] || {})
|
|
45
|
+
: await pg.query(
|
|
46
|
+
'select content_id as cid, status from site.contents where content_type_id=$1 limit 1',
|
|
47
|
+
[ctid],
|
|
48
|
+
).then(el => el.rows?.[0] || {});
|
|
49
|
+
|
|
50
|
+
if (!cid) {
|
|
51
|
+
return reply.status(404).send('contents not found');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (cid === 'pages') {
|
|
55
|
+
return reply.status(403).send('access restricted: pages contents cannot be deleted');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (status === 'archived') {
|
|
59
|
+
const row = await dataDelete({
|
|
60
|
+
pg,
|
|
61
|
+
id,
|
|
62
|
+
table: 'site.contents',
|
|
63
|
+
referer: headers?.referer,
|
|
64
|
+
uid: user?.uid || 0,
|
|
65
|
+
})
|
|
66
|
+
return { id, ...row || {} };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// archived
|
|
70
|
+
const row = await dataUpdate({
|
|
71
|
+
pg,
|
|
72
|
+
id,
|
|
73
|
+
table: 'site.contents',
|
|
74
|
+
data: {
|
|
75
|
+
status: 'archived',
|
|
76
|
+
},
|
|
77
|
+
referer: headers?.referer,
|
|
78
|
+
uid: user?.uid || 0,
|
|
79
|
+
})
|
|
80
|
+
return { id, ...row || {} };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// custom table
|
|
84
|
+
if (!table && !dbtable) {
|
|
85
|
+
return reply.status(400).send('invalid params: type');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const status = await pg.query(`select status from data."${(table || dbtable)}" where id =$1`, [id]).then(el => el.rows?.[0]?.status);
|
|
89
|
+
|
|
90
|
+
if (status === 'archived') {
|
|
91
|
+
const row = await dataDelete({
|
|
92
|
+
pg,
|
|
93
|
+
id,
|
|
94
|
+
table: 'data.' + `"${(table || dbtable)}"`,
|
|
95
|
+
referer: headers?.referer,
|
|
96
|
+
uid: user?.uid || 0,
|
|
97
|
+
})
|
|
98
|
+
return { id, ...row || {} };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// archived
|
|
102
|
+
const result = await dataUpdate({
|
|
103
|
+
pg,
|
|
104
|
+
id,
|
|
105
|
+
table: 'data.' + `"${(table || dbtable)}"`,
|
|
106
|
+
data: {
|
|
107
|
+
status: 'archived',
|
|
108
|
+
},
|
|
109
|
+
referer: headers?.referer,
|
|
110
|
+
uid: user?.uid || 0,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const pk = pg.pk?.['data.' + table || dbtable];
|
|
114
|
+
return reply.status(200).send({ id: result?.[pk], ...result || {} });
|
|
60
115
|
}
|
|
@@ -1,47 +1,76 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { rm } from 'node:fs/promises';
|
|
4
|
-
|
|
5
|
-
import { config, dataDelete, getFolder, pgClients } from "@opengis/fastify-table/utils.js";
|
|
6
|
-
|
|
7
|
-
const rootDir = getFolder(config, 'local');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { rm, stat, readdir } from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
import { config, dataDelete, getFolder, pgClients } from "@opengis/fastify-table/utils.js";
|
|
6
|
+
|
|
7
|
+
const rootDir = getFolder(config, 'local');
|
|
8
|
+
const dir = '/files';
|
|
9
|
+
|
|
10
|
+
export default async function deleteMedia({
|
|
11
|
+
pg = pgClients.client, params = {}, query = {}, user = {}, method = 'DELETE',
|
|
12
|
+
}, reply) {
|
|
13
|
+
if (!config.debug && method !== 'DELETE') {
|
|
14
|
+
return reply.status(403).send('access restricted');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!params.id && query.subdir) {
|
|
18
|
+
const dirpath = path.join(rootDir, dir, query.subdir || '');
|
|
19
|
+
const exists = existsSync(dirpath);
|
|
20
|
+
const stats = await stat(dirpath);
|
|
21
|
+
|
|
22
|
+
if (!stats.isDirectory()) {
|
|
23
|
+
return reply.status(400).send('not a directory');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!exists) {
|
|
27
|
+
return reply.status(404).send('subdir not found: ' + query.subdir);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const content = await readdir(dirpath, { recursive: true });
|
|
31
|
+
|
|
32
|
+
if (query.subdir.startsWith('uploads') && !query.subdir.split('/')[1]) {
|
|
33
|
+
return reply.status(403).send('access restricted: uploads directory');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// only admins are allowed to delete non-empty directories
|
|
37
|
+
if (content?.length && !user?.user_type?.includes?.('admin')) {
|
|
38
|
+
return reply.status(400).send('directory is not empty');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await rm(dirpath, { recursive: true });
|
|
42
|
+
return reply.status(200).send('subdirectory successfully deleted');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!params?.id) {
|
|
46
|
+
return reply.status(400).send('not enough params: id');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!pg.pk?.['site.media']) {
|
|
50
|
+
return reply.status(404).send('table not found');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { url: relpath, id } = await pg.query(
|
|
54
|
+
'select media_id as id, url from site.media where media_id = $1 and url is not null',
|
|
55
|
+
[params.id],
|
|
56
|
+
).then(el => el.rows?.[0] || {});
|
|
57
|
+
|
|
58
|
+
if (!id) {
|
|
59
|
+
return reply.status(404).send('media not found: ' + params.id);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const res = await dataDelete({
|
|
63
|
+
pg,
|
|
64
|
+
id,
|
|
65
|
+
table: 'site.media',
|
|
66
|
+
uid: user?.uid || 0,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const filepath = path.join(rootDir, relpath);
|
|
70
|
+
|
|
71
|
+
if (existsSync(filepath)) {
|
|
72
|
+
await rm(filepath, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { id, ...res || {} };
|
|
47
76
|
}
|