@opengis/cms 0.0.41 → 0.0.43
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/index.js +8518 -8072
- package/dist/index.umd.cjs +75 -74
- package/input-types.json +10 -0
- package/package.json +5 -11
- package/plugin.js +3 -36
- package/server/app.js +1 -1
- package/server/plugins/adminHook.js +1 -1
- package/server/plugins/hook.js +1 -1
- package/server/routes/cms/controllers/deleteMedia.js +76 -76
- package/server/routes/cms/controllers/downloadMedia.js +49 -49
- package/server/routes/cms/controllers/getContentBySlug.js +1 -3
- package/server/routes/cms/controllers/getPermissions.js +15 -15
- package/server/routes/cms/controllers/insertContent.js +1 -2
- package/server/routes/cms/controllers/listMedia.js +94 -94
- package/server/routes/cms/controllers/metadataMedia.js +39 -39
- package/server/routes/cms/controllers/setPermissions.js +49 -49
- package/server/routes/cms/controllers/updateContent.js +1 -3
- package/server/routes/cms/controllers/uploadMedia.js +79 -79
- package/server/routes/cms/index.mjs +1 -3
- package/server/routes/cms/utils/getSingle.js +1 -3
- package/server/routes/cms/utils/inputTypes.js +6 -0
- package/server/routes/contentType/utils/updateCustomContentTable.js +1 -3
- package/server/routes/root.mjs +1 -1
- package/server/templates/select/core.user_mentioned.sql +1 -1
package/input-types.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengis/cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.43",
|
|
4
4
|
"description": "cms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Softpro",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"module",
|
|
11
11
|
"dist",
|
|
12
12
|
"server",
|
|
13
|
-
"plugin.js"
|
|
13
|
+
"plugin.js",
|
|
14
|
+
"input-types.json"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
17
|
"patch": "npm version patch && git push && npm publish",
|
|
@@ -21,21 +22,14 @@
|
|
|
21
22
|
"build": "vite build && vite build admin",
|
|
22
23
|
"build:lib": "vite build",
|
|
23
24
|
"proxy": "vite dev admin",
|
|
24
|
-
"build-npm": "vite build",
|
|
25
25
|
"start": "bun --env-file=.env.ip server",
|
|
26
|
-
"prod-ip1": "node server --config=ip ",
|
|
27
|
-
"prod-softpro1": "node server --config=softpro",
|
|
28
26
|
"prod": "NODE_ENV=production bun --env-file=.env.ip server ",
|
|
29
|
-
"
|
|
30
|
-
"prod-ip-test": "PORT=3025 node server --config=ip",
|
|
31
|
-
"debug": "bun --watch server --config=softpro",
|
|
32
|
-
"ip-test": "node server --config=ip-test",
|
|
33
|
-
"ip": "node --env-file=.env.ip server",
|
|
27
|
+
"ip": "bun --env-file=.env.ip server",
|
|
34
28
|
"demo": "node --env-file=.env.demo --env-file=.env server",
|
|
35
29
|
"i18n:sync": "node i18n-sync.cjs",
|
|
36
30
|
"prepublishOnly": "bun build:lib",
|
|
37
31
|
"softpro": "bun --env-file=.env.softpro server",
|
|
38
|
-
"softpro1": "bun --env-file=.env.prod-softpro.local server"
|
|
32
|
+
"softpro1": "NODE_ENV=production bun --env-file=.env.prod-softpro.local server"
|
|
39
33
|
},
|
|
40
34
|
"dependencies": {},
|
|
41
35
|
"resolutions": {
|
package/plugin.js
CHANGED
|
@@ -1,29 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
4
3
|
|
|
5
|
-
import { config, execMigrations, getPGAsync } from '@opengis/fastify-table/utils.js';
|
|
6
|
-
|
|
4
|
+
import { config, execMigrations, getPGAsync, addTemplateDir } from '@opengis/fastify-table/utils.js';
|
|
5
|
+
// for cms as plugin only
|
|
6
|
+
addTemplateDir(path.join(process.cwd(), 'node_modules/@opengis/cms/module/cms'));
|
|
7
7
|
|
|
8
8
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
|
|
10
|
-
// node server --config=softpro
|
|
11
|
-
console.log(process.argv);
|
|
12
|
-
if (process.argv.find(el => el.includes('--config'))) {
|
|
13
|
-
const filename = process.argv.find(el => el.includes('--config')).split('=').pop();
|
|
14
|
-
const cmsConfig = existsSync(`config/${filename}.json`)
|
|
15
|
-
? JSON.parse(readFileSync(`config/${filename}.json`, 'utf8'))
|
|
16
|
-
: {};
|
|
17
|
-
|
|
18
|
-
console.log('cms init', cmsConfig.pg?.database);
|
|
19
|
-
Object.keys(cmsConfig).forEach(key => {
|
|
20
|
-
config[key] = cmsConfig[key];
|
|
21
|
-
});
|
|
22
|
-
config.spacename = 'default' || cmsConfig.name || filename;
|
|
23
|
-
} else {
|
|
24
|
-
config.spacename = 'default' || config.spacename || process.env.NODE_ENV; // --env.file=.env.ip
|
|
25
|
-
}
|
|
26
|
-
|
|
27
10
|
const { prefix = '/api' } = config;
|
|
28
11
|
|
|
29
12
|
export default async function (app) {
|
|
@@ -56,21 +39,5 @@ export default async function (app) {
|
|
|
56
39
|
await pg.query(`alter table data.${table} add column if not exists is_pin boolean DEFAULT false`).catch(err => console.log(err));
|
|
57
40
|
await pg.query(`alter table data.${table} drop column if exists publish_at`).catch(err => console.log(err));
|
|
58
41
|
}));
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/*customTables.filter(table => pg.pk?.[`data.${table}`])?.forEach(table => pg.query(`alter table data.${table} add column if not exists meta json`).catch(err => console.log(err)));
|
|
63
|
-
customTables.filter(table => pg.pk?.[`data.${table}`])?.forEach(table => pg.query(`alter table data.${table} add column if not exists published_at timestamp without time zone not null default now()`).catch(err => console.log(err)));
|
|
64
|
-
customTables.filter(table => pg.pk?.[`data.${table}`])?.forEach(table => pg.query(`alter table data.${table} add column if not exists is_pin boolean DEFAULT false`).catch(err => console.log(err)));*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
/* if (pg?.pk?.['site.content_types'] && pg?.pk?.['site.contents']) {
|
|
69
|
-
await pg.query('insert into site.contents(content_type_id, space_id, locale) select content_type_id, $1, $2 from site.content_types where content_type_id not in (select content_type_id from site.contents)', [config.spacename || 'default', config.locale || 'ua']).catch(err => {
|
|
70
|
-
console.log(err.toString());
|
|
71
|
-
});
|
|
72
42
|
}
|
|
73
|
-
if (pg?.pk?.['site.spaces']) {
|
|
74
|
-
pg.query('insert into site.spaces(space_id, name) values($1, $1) on conflict(space_id) do nothing', [config.spacename || 'default']).catch(err => console.error('spaces insert error', err.toString())); // for content site.spaces
|
|
75
|
-
} */
|
|
76
43
|
}
|
package/server/app.js
CHANGED
|
@@ -13,7 +13,7 @@ config.auth['2fa'].sufix = config.auth['2fa'].sufix || 'login';
|
|
|
13
13
|
// config.auth.loginPage = true; // disable core loginPage api, use vue component instead
|
|
14
14
|
// config.auth.link = config.auth.link || { '2fa': { login: '/2fa' } };
|
|
15
15
|
|
|
16
|
-
export default
|
|
16
|
+
export default function (app) {
|
|
17
17
|
// core
|
|
18
18
|
app.register(import('@fastify/compress'), { encodings: ['br', 'gzip'], });
|
|
19
19
|
app.register(import('./plugins/adminHook.js'));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fp from 'fastify-plugin';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
function plugin(fastify) {
|
|
5
5
|
// preSerialization
|
|
6
6
|
fastify.addHook('preSerialization', async (req, reply, payload) => {
|
|
7
7
|
/*if (req.url.includes('/suggest/') && !req.query.json) {
|
package/server/plugins/hook.js
CHANGED
|
@@ -40,7 +40,7 @@ async function updateDelayPublished() {
|
|
|
40
40
|
if (config.trace) console.log(r);
|
|
41
41
|
return r;
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
function plugin(app) {
|
|
44
44
|
addHook('afterUser', async ({ payload = {} }) => {
|
|
45
45
|
if (payload?.user) {
|
|
46
46
|
// get from cache, refresh hourly
|
|
@@ -1,77 +1,77 @@
|
|
|
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
|
-
// path.resolve() converts POSIX paths from getFolder to valid Windows paths (Bun/Node fs require this on Windows)
|
|
8
|
-
const rootDir = path.resolve(getFolder(config, 'local'));
|
|
9
|
-
const dir = '/files';
|
|
10
|
-
|
|
11
|
-
export default async function deleteMedia({
|
|
12
|
-
pg = pgClients.client, params = {}, query = {}, user = {}, method = 'DELETE',
|
|
13
|
-
}, reply) {
|
|
14
|
-
if (!config.debug && method !== 'DELETE') {
|
|
15
|
-
return reply.status(403).send('access restricted');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (!params.id && query.subdir) {
|
|
19
|
-
const dirpath = path.join(rootDir, dir, query.subdir || '');
|
|
20
|
-
const exists = existsSync(dirpath);
|
|
21
|
-
const stats = await stat(dirpath);
|
|
22
|
-
|
|
23
|
-
if (!stats.isDirectory()) {
|
|
24
|
-
return reply.status(400).send('not a directory');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!exists) {
|
|
28
|
-
return reply.status(404).send('subdir not found: ' + query.subdir);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const content = await readdir(dirpath, { recursive: true });
|
|
32
|
-
|
|
33
|
-
if (query.subdir.startsWith('uploads') && !query.subdir.split('/')[1]) {
|
|
34
|
-
return reply.status(403).send('access restricted: uploads directory');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// only admins are allowed to delete non-empty directories
|
|
38
|
-
if (content?.length && !user?.user_type?.includes?.('admin')) {
|
|
39
|
-
return reply.status(400).send('directory is not empty');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
await rm(dirpath, { recursive: true });
|
|
43
|
-
return reply.status(200).send('subdirectory successfully deleted');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!params?.id) {
|
|
47
|
-
return reply.status(400).send('not enough params: id');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!pg.pk?.['site.media']) {
|
|
51
|
-
return reply.status(404).send('table not found');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const { url: relpath, id } = await pg.query(
|
|
55
|
-
'select media_id as id, url from site.media where media_id = $1 and url is not null',
|
|
56
|
-
[params.id],
|
|
57
|
-
).then(el => el.rows?.[0] || {});
|
|
58
|
-
|
|
59
|
-
if (!id) {
|
|
60
|
-
return reply.status(404).send('media not found: ' + params.id);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const res = await dataDelete({
|
|
64
|
-
pg,
|
|
65
|
-
id,
|
|
66
|
-
table: 'site.media',
|
|
67
|
-
uid: user?.uid || 0,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const filepath = path.join(rootDir, relpath);
|
|
71
|
-
|
|
72
|
-
if (existsSync(filepath)) {
|
|
73
|
-
await rm(filepath, { recursive: true });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return { id, ...res || {} };
|
|
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
|
+
// path.resolve() converts POSIX paths from getFolder to valid Windows paths (Bun/Node fs require this on Windows)
|
|
8
|
+
const rootDir = path.resolve(getFolder(config, 'local'));
|
|
9
|
+
const dir = '/files';
|
|
10
|
+
|
|
11
|
+
export default async function deleteMedia({
|
|
12
|
+
pg = pgClients.client, params = {}, query = {}, user = {}, method = 'DELETE',
|
|
13
|
+
}, reply) {
|
|
14
|
+
if (!config.debug && method !== 'DELETE') {
|
|
15
|
+
return reply.status(403).send('access restricted');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!params.id && query.subdir) {
|
|
19
|
+
const dirpath = path.join(rootDir, dir, query.subdir || '');
|
|
20
|
+
const exists = existsSync(dirpath);
|
|
21
|
+
const stats = await stat(dirpath);
|
|
22
|
+
|
|
23
|
+
if (!stats.isDirectory()) {
|
|
24
|
+
return reply.status(400).send('not a directory');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!exists) {
|
|
28
|
+
return reply.status(404).send('subdir not found: ' + query.subdir);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const content = await readdir(dirpath, { recursive: true });
|
|
32
|
+
|
|
33
|
+
if (query.subdir.startsWith('uploads') && !query.subdir.split('/')[1]) {
|
|
34
|
+
return reply.status(403).send('access restricted: uploads directory');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// only admins are allowed to delete non-empty directories
|
|
38
|
+
if (content?.length && !user?.user_type?.includes?.('admin')) {
|
|
39
|
+
return reply.status(400).send('directory is not empty');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await rm(dirpath, { recursive: true });
|
|
43
|
+
return reply.status(200).send('subdirectory successfully deleted');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!params?.id) {
|
|
47
|
+
return reply.status(400).send('not enough params: id');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!pg.pk?.['site.media']) {
|
|
51
|
+
return reply.status(404).send('table not found');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { url: relpath, id } = await pg.query(
|
|
55
|
+
'select media_id as id, url from site.media where media_id = $1 and url is not null',
|
|
56
|
+
[params.id],
|
|
57
|
+
).then(el => el.rows?.[0] || {});
|
|
58
|
+
|
|
59
|
+
if (!id) {
|
|
60
|
+
return reply.status(404).send('media not found: ' + params.id);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const res = await dataDelete({
|
|
64
|
+
pg,
|
|
65
|
+
id,
|
|
66
|
+
table: 'site.media',
|
|
67
|
+
uid: user?.uid || 0,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const filepath = path.join(rootDir, relpath);
|
|
71
|
+
|
|
72
|
+
if (existsSync(filepath)) {
|
|
73
|
+
await rm(filepath, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { id, ...res || {} };
|
|
77
77
|
}
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
|
-
|
|
5
|
-
import { config, getFolder, pgClients } from "@opengis/fastify-table/utils.js";
|
|
6
|
-
|
|
7
|
-
// path.resolve() converts POSIX paths from getFolder to valid Windows paths (Bun/Node fs require this on Windows)
|
|
8
|
-
const rootDir = path.resolve(getFolder(config, 'local'));
|
|
9
|
-
|
|
10
|
-
export default async function downloadMedia({
|
|
11
|
-
pg = pgClients.client, params = {},
|
|
12
|
-
}, reply) {
|
|
13
|
-
if (!params?.id) {
|
|
14
|
-
return reply.status(400).send('not enough params: id');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (!pg.pk?.['site.media']) {
|
|
18
|
-
return reply.status(404).send('table not found');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const { filename, mime, id, url: relpath } = await pg.query(
|
|
22
|
-
'select media_id as id, filename, mime, url from site.media where media_id = $1 and url is not null',
|
|
23
|
-
[params.id],
|
|
24
|
-
).then(el => el.rows?.[0] || {});
|
|
25
|
-
|
|
26
|
-
if (!id) {
|
|
27
|
-
return reply.status(404).send('media not found: ' + params.id);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const filepath = path.join(rootDir, relpath);
|
|
31
|
-
|
|
32
|
-
if (!existsSync(filepath)) {
|
|
33
|
-
return reply.status(404).send('file not found');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const buffer = await readFile(filepath, { buffer: true });
|
|
37
|
-
|
|
38
|
-
// skip xml load for preview
|
|
39
|
-
if (params.type === 'preview' && path.extname(filename) !== '.xml') {
|
|
40
|
-
return reply.headers({ 'Content-Type': mime }).send(buffer);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return reply
|
|
44
|
-
.headers({
|
|
45
|
-
'Content-Type': mime,
|
|
46
|
-
'Content-Disposition': `attachment; filename=${filename || path.basename(filepath)}`,
|
|
47
|
-
})
|
|
48
|
-
.send(buffer);
|
|
49
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
import { config, getFolder, pgClients } from "@opengis/fastify-table/utils.js";
|
|
6
|
+
|
|
7
|
+
// path.resolve() converts POSIX paths from getFolder to valid Windows paths (Bun/Node fs require this on Windows)
|
|
8
|
+
const rootDir = path.resolve(getFolder(config, 'local'));
|
|
9
|
+
|
|
10
|
+
export default async function downloadMedia({
|
|
11
|
+
pg = pgClients.client, params = {},
|
|
12
|
+
}, reply) {
|
|
13
|
+
if (!params?.id) {
|
|
14
|
+
return reply.status(400).send('not enough params: id');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!pg.pk?.['site.media']) {
|
|
18
|
+
return reply.status(404).send('table not found');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { filename, mime, id, url: relpath } = await pg.query(
|
|
22
|
+
'select media_id as id, filename, mime, url from site.media where media_id = $1 and url is not null',
|
|
23
|
+
[params.id],
|
|
24
|
+
).then(el => el.rows?.[0] || {});
|
|
25
|
+
|
|
26
|
+
if (!id) {
|
|
27
|
+
return reply.status(404).send('media not found: ' + params.id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const filepath = path.join(rootDir, relpath);
|
|
31
|
+
|
|
32
|
+
if (!existsSync(filepath)) {
|
|
33
|
+
return reply.status(404).send('file not found');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const buffer = await readFile(filepath, { buffer: true });
|
|
37
|
+
|
|
38
|
+
// skip xml load for preview
|
|
39
|
+
if (params.type === 'preview' && path.extname(filename) !== '.xml') {
|
|
40
|
+
return reply.headers({ 'Content-Type': mime }).send(buffer);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return reply
|
|
44
|
+
.headers({
|
|
45
|
+
'Content-Type': mime,
|
|
46
|
+
'Content-Disposition': `attachment; filename=${filename || path.basename(filepath)}`,
|
|
47
|
+
})
|
|
48
|
+
.send(buffer);
|
|
49
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
|
|
3
1
|
import { config, pgClients } from '@opengis/fastify-table/utils.js';
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
import inputTypes from '../utils/inputTypes.js';
|
|
6
4
|
|
|
7
5
|
export default async function getContentBySlug({ pg = pgClients.client, params = {}, headers = {} }, reply) {
|
|
8
6
|
if (!params.slug) {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { pgClients } from '@opengis/fastify-table/utils.js';
|
|
2
|
-
|
|
3
|
-
export default async function getPermissions(req, reply) {
|
|
4
|
-
const { pg = pgClients.client, params = {}, user = {} } = req;
|
|
5
|
-
|
|
6
|
-
if (!user?.uid) {
|
|
7
|
-
return reply.status(401).send('unauthorized');
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { rows = [] } = await pg.query(
|
|
11
|
-
`select * from site.permissions where ${params.id ? 'user_id=$1' : 'true'}`,
|
|
12
|
-
[params.id].filter(Boolean),
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
return { permissions: rows };
|
|
1
|
+
import { pgClients } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
export default async function getPermissions(req, reply) {
|
|
4
|
+
const { pg = pgClients.client, params = {}, user = {} } = req;
|
|
5
|
+
|
|
6
|
+
if (!user?.uid) {
|
|
7
|
+
return reply.status(401).send('unauthorized');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { rows = [] } = await pg.query(
|
|
11
|
+
`select * from site.permissions where ${params.id ? 'user_id=$1' : 'true'}`,
|
|
12
|
+
[params.id].filter(Boolean),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return { permissions: rows };
|
|
16
16
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { config, checkSQL, getTemplate, pgClients, dataInsert, logger } from '@opengis/fastify-table/utils.js';
|
|
2
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import inputTypes from '../utils/inputTypes.js';
|
|
5
4
|
|
|
6
5
|
import updateLocalization from '../utils/updateLocalization.js';
|
|
7
6
|
|
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { readdir, stat } from 'node:fs/promises';
|
|
4
|
-
|
|
5
|
-
import { config, getFolder, pgClients, getMimeType } from '@opengis/fastify-table/utils.js';
|
|
6
|
-
import { createHash } from 'node:crypto';
|
|
7
|
-
|
|
8
|
-
// path.resolve() converts POSIX paths from getFolder to valid Windows paths (Bun/Node fs require this on Windows)
|
|
9
|
-
const rootDir = path.resolve(getFolder(config, 'local'));
|
|
10
|
-
const dir = '/files';
|
|
11
|
-
|
|
12
|
-
mkdirSync(path.join(rootDir, dir), { recursive: true });
|
|
13
|
-
|
|
14
|
-
export default async function listMedia(req, reply) {
|
|
15
|
-
const { pg = pgClients.client, query = {} } = req;
|
|
16
|
-
const { subdir: subdir1 = '', search } = query;
|
|
17
|
-
|
|
18
|
-
if (!pg.pk?.['site.media']) {
|
|
19
|
-
return reply.status(404).send('table not found');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (typeof subdir1 !== 'string' || subdir1.includes('..')) {
|
|
23
|
-
return reply.status(403).send('invalid params: subdir');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const subdir = subdir1.replace(/\/{2,}/g, '/');
|
|
27
|
-
const relpath = path.join(dir, subdir1).replace(/\\/g, '/');
|
|
28
|
-
const dirpath = path.join(rootDir, relpath).replace(/\\/g, '/');
|
|
29
|
-
|
|
30
|
-
if (!existsSync(dirpath)) {
|
|
31
|
-
return { data: [], relpath, msg: 'directory not exists' };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const isDirectory = (await stat(dirpath)).isDirectory();
|
|
35
|
-
|
|
36
|
-
const allItems = isDirectory ? await readdir(dirpath, { withFileTypes: true, recursive: true }) : [];
|
|
37
|
-
const items = isDirectory ? await readdir(dirpath, { withFileTypes: true }) : [];
|
|
38
|
-
|
|
39
|
-
const rows = await pg.query(
|
|
40
|
-
`select
|
|
41
|
-
media_id as id, filename, filetype, filesize, url, description, alt,
|
|
42
|
-
mime, preview_url, created_at, updated_at, created_by, updated_by
|
|
43
|
-
from site.media
|
|
44
|
-
where ${subdir ? 'subdir = $1' : (search ? '1=1' : 'subdir is null')} ${search ? `and filename ilike '%${search.replace(/'/g, "''")}%'` : ''}`,
|
|
45
|
-
[subdir].filter(Boolean),
|
|
46
|
-
).then(el => el.rows || []); // ?.filter(row => items.map(el => el.name).includes(row.filename))
|
|
47
|
-
|
|
48
|
-
const subdirs = items
|
|
49
|
-
.filter(el => el.isDirectory())
|
|
50
|
-
.map(el => ({ type: 'dir', name: el.name }))
|
|
51
|
-
.filter(el => search ? el.name.includes(search) : true);
|
|
52
|
-
|
|
53
|
-
if (subdirs.length) {
|
|
54
|
-
await Promise.all(subdirs.map(async (item) => {
|
|
55
|
-
const items = isDirectory ? await readdir(path.join(dirpath, item.name)) : [];
|
|
56
|
-
Object.assign(item, { count: items.length });
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const result = { relpath };
|
|
61
|
-
|
|
62
|
-
if (config.debug) {
|
|
63
|
-
Object.assign(result, { rootDir });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const files = (search ? allItems.filter(el => el.name.includes(search)) : items)
|
|
67
|
-
.filter(el => el.isFile())
|
|
68
|
-
.map(el => {
|
|
69
|
-
const media = rows.find(row => row.filename === el.name);
|
|
70
|
-
const filepath = media ? media.url : ('/files/' + (el.path.split('/files/')[1] ? el.path.split('/files/')[1] + '/' : '') + el.name);
|
|
71
|
-
return media ? {
|
|
72
|
-
type: 'file',
|
|
73
|
-
...media,
|
|
74
|
-
url: `${req.routeOptions.url}/${media.id}/file`,
|
|
75
|
-
preview: `${req.routeOptions.url}/${media.id}/preview`,
|
|
76
|
-
filepath,
|
|
77
|
-
metadata: `${req.routeOptions.url}/${media.id}`,
|
|
78
|
-
} : {
|
|
79
|
-
id: createHash('md5').update(filepath).digest('hex'),
|
|
80
|
-
hash: true,
|
|
81
|
-
type: 'file',
|
|
82
|
-
filename: el.name,
|
|
83
|
-
filepath,
|
|
84
|
-
filetype: getMimeType(el.name)?.startsWith('image/') ? "image" : "other",
|
|
85
|
-
filesize: 0,
|
|
86
|
-
url: filepath,
|
|
87
|
-
mime: getMimeType(el.name),
|
|
88
|
-
preview: filepath,
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
Object.assign(result, { data: subdirs.concat(files) });
|
|
93
|
-
return result;
|
|
94
|
-
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
import { config, getFolder, pgClients, getMimeType } from '@opengis/fastify-table/utils.js';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
// path.resolve() converts POSIX paths from getFolder to valid Windows paths (Bun/Node fs require this on Windows)
|
|
9
|
+
const rootDir = path.resolve(getFolder(config, 'local'));
|
|
10
|
+
const dir = '/files';
|
|
11
|
+
|
|
12
|
+
mkdirSync(path.join(rootDir, dir), { recursive: true });
|
|
13
|
+
|
|
14
|
+
export default async function listMedia(req, reply) {
|
|
15
|
+
const { pg = pgClients.client, query = {} } = req;
|
|
16
|
+
const { subdir: subdir1 = '', search } = query;
|
|
17
|
+
|
|
18
|
+
if (!pg.pk?.['site.media']) {
|
|
19
|
+
return reply.status(404).send('table not found');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof subdir1 !== 'string' || subdir1.includes('..')) {
|
|
23
|
+
return reply.status(403).send('invalid params: subdir');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const subdir = subdir1.replace(/\/{2,}/g, '/');
|
|
27
|
+
const relpath = path.join(dir, subdir1).replace(/\\/g, '/');
|
|
28
|
+
const dirpath = path.join(rootDir, relpath).replace(/\\/g, '/');
|
|
29
|
+
|
|
30
|
+
if (!existsSync(dirpath)) {
|
|
31
|
+
return { data: [], relpath, msg: 'directory not exists' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const isDirectory = (await stat(dirpath)).isDirectory();
|
|
35
|
+
|
|
36
|
+
const allItems = isDirectory ? await readdir(dirpath, { withFileTypes: true, recursive: true }) : [];
|
|
37
|
+
const items = isDirectory ? await readdir(dirpath, { withFileTypes: true }) : [];
|
|
38
|
+
|
|
39
|
+
const rows = await pg.query(
|
|
40
|
+
`select
|
|
41
|
+
media_id as id, filename, filetype, filesize, url, description, alt,
|
|
42
|
+
mime, preview_url, created_at, updated_at, created_by, updated_by
|
|
43
|
+
from site.media
|
|
44
|
+
where ${subdir ? 'subdir = $1' : (search ? '1=1' : 'subdir is null')} ${search ? `and filename ilike '%${search.replace(/'/g, "''")}%'` : ''}`,
|
|
45
|
+
[subdir].filter(Boolean),
|
|
46
|
+
).then(el => el.rows || []); // ?.filter(row => items.map(el => el.name).includes(row.filename))
|
|
47
|
+
|
|
48
|
+
const subdirs = items
|
|
49
|
+
.filter(el => el.isDirectory())
|
|
50
|
+
.map(el => ({ type: 'dir', name: el.name }))
|
|
51
|
+
.filter(el => search ? el.name.includes(search) : true);
|
|
52
|
+
|
|
53
|
+
if (subdirs.length) {
|
|
54
|
+
await Promise.all(subdirs.map(async (item) => {
|
|
55
|
+
const items = isDirectory ? await readdir(path.join(dirpath, item.name)) : [];
|
|
56
|
+
Object.assign(item, { count: items.length });
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const result = { relpath };
|
|
61
|
+
|
|
62
|
+
if (config.debug) {
|
|
63
|
+
Object.assign(result, { rootDir });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const files = (search ? allItems.filter(el => el.name.includes(search)) : items)
|
|
67
|
+
.filter(el => el.isFile())
|
|
68
|
+
.map(el => {
|
|
69
|
+
const media = rows.find(row => row.filename === el.name);
|
|
70
|
+
const filepath = media ? media.url : ('/files/' + (el.path.split('/files/')[1] ? el.path.split('/files/')[1] + '/' : '') + el.name);
|
|
71
|
+
return media ? {
|
|
72
|
+
type: 'file',
|
|
73
|
+
...media,
|
|
74
|
+
url: `${req.routeOptions.url}/${media.id}/file`,
|
|
75
|
+
preview: `${req.routeOptions.url}/${media.id}/preview`,
|
|
76
|
+
filepath,
|
|
77
|
+
metadata: `${req.routeOptions.url}/${media.id}`,
|
|
78
|
+
} : {
|
|
79
|
+
id: createHash('md5').update(filepath).digest('hex'),
|
|
80
|
+
hash: true,
|
|
81
|
+
type: 'file',
|
|
82
|
+
filename: el.name,
|
|
83
|
+
filepath,
|
|
84
|
+
filetype: getMimeType(el.name)?.startsWith('image/') ? "image" : "other",
|
|
85
|
+
filesize: 0,
|
|
86
|
+
url: filepath,
|
|
87
|
+
mime: getMimeType(el.name),
|
|
88
|
+
preview: filepath,
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
Object.assign(result, { data: subdirs.concat(files) });
|
|
93
|
+
return result;
|
|
94
|
+
|
|
95
95
|
}
|