@plank-cms/plank 0.18.0 → 0.19.0
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/admin/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
href="https://fonts.googleapis.com/css2?family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&display=swap"
|
|
13
13
|
rel="stylesheet"
|
|
14
14
|
/>
|
|
15
|
-
<script type="module" crossorigin src="/admin/assets/index-
|
|
15
|
+
<script type="module" crossorigin src="/admin/assets/index-BepYvDmW.js"></script>
|
|
16
16
|
<link rel="stylesheet" crossorigin href="/admin/assets/index-FkEexpp5.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { randomBytes } from "crypto";
|
|
|
7
7
|
import { resolve, join } from "path";
|
|
8
8
|
import fs from "fs-extra";
|
|
9
9
|
import { execa } from "execa";
|
|
10
|
-
var PACKAGE_VERSION = "0.
|
|
10
|
+
var PACKAGE_VERSION = "0.19.0";
|
|
11
11
|
function generateSecret() {
|
|
12
12
|
return randomBytes(32).toString("hex");
|
|
13
13
|
}
|
|
@@ -101,7 +101,7 @@ import { dirname, join as join2, resolve as resolve2 } from "path";
|
|
|
101
101
|
async function start() {
|
|
102
102
|
config({ path: resolve2(process.cwd(), ".env") });
|
|
103
103
|
process.env.PLANK_ADMIN_DIST = join2(dirname(fileURLToPath(import.meta.url)), "admin");
|
|
104
|
-
const { start: startServer } = await import("./server-
|
|
104
|
+
const { start: startServer } = await import("./server-KVOZGN2H.js");
|
|
105
105
|
await startServer();
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -4662,9 +4662,71 @@ var SYSTEM_RESPONSE_FIELDS = [
|
|
|
4662
4662
|
"editor"
|
|
4663
4663
|
];
|
|
4664
4664
|
function parseCsvParam(value) {
|
|
4665
|
-
if (typeof value
|
|
4666
|
-
return
|
|
4667
|
-
|
|
4665
|
+
if (typeof value === "string") {
|
|
4666
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
4667
|
+
}
|
|
4668
|
+
if (Array.isArray(value)) {
|
|
4669
|
+
return value.flatMap((item) => parseCsvParam(item));
|
|
4670
|
+
}
|
|
4671
|
+
return [];
|
|
4672
|
+
}
|
|
4673
|
+
function coerceFilterValue(raw, field) {
|
|
4674
|
+
if (field.type === "number") {
|
|
4675
|
+
const parsed = field.subtype === "float" ? Number.parseFloat(raw) : Number.parseInt(raw, 10);
|
|
4676
|
+
return Number.isNaN(parsed) ? raw : parsed;
|
|
4677
|
+
}
|
|
4678
|
+
if (field.type === "boolean") {
|
|
4679
|
+
if (raw === "true")
|
|
4680
|
+
return true;
|
|
4681
|
+
if (raw === "false")
|
|
4682
|
+
return false;
|
|
4683
|
+
}
|
|
4684
|
+
return raw;
|
|
4685
|
+
}
|
|
4686
|
+
function coerceFilterValues(value, field) {
|
|
4687
|
+
return parseCsvParam(value).map((item) => coerceFilterValue(item, field));
|
|
4688
|
+
}
|
|
4689
|
+
function isFilterOperator(value) {
|
|
4690
|
+
return value === "eq" || value === "ne" || value === "in" || value === "nin";
|
|
4691
|
+
}
|
|
4692
|
+
function parseFilters(query, fieldMap) {
|
|
4693
|
+
const filters = [];
|
|
4694
|
+
const invalidFilters = [];
|
|
4695
|
+
const filtersObject = query.filters && typeof query.filters === "object" && !Array.isArray(query.filters) ? query.filters : null;
|
|
4696
|
+
if (filtersObject) {
|
|
4697
|
+
for (const [fieldName, operatorValue] of Object.entries(filtersObject)) {
|
|
4698
|
+
const field = fieldMap.get(fieldName);
|
|
4699
|
+
if (!field || typeof operatorValue !== "object" || operatorValue === null || Array.isArray(operatorValue)) {
|
|
4700
|
+
invalidFilters.push(`filters.${fieldName}`);
|
|
4701
|
+
continue;
|
|
4702
|
+
}
|
|
4703
|
+
for (const [operatorKey, rawValue] of Object.entries(operatorValue)) {
|
|
4704
|
+
if (!isFilterOperator(operatorKey)) {
|
|
4705
|
+
invalidFilters.push(`filters.${fieldName}.${operatorKey}`);
|
|
4706
|
+
continue;
|
|
4707
|
+
}
|
|
4708
|
+
filters.push({
|
|
4709
|
+
field,
|
|
4710
|
+
operator: operatorKey,
|
|
4711
|
+
rawValue,
|
|
4712
|
+
rawKey: `filters[${fieldName}][${operatorKey}]`
|
|
4713
|
+
});
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
for (const [key, rawValue] of Object.entries(query)) {
|
|
4718
|
+
const match = /^filters\[([^\]]+)\]\[([^\]]+)\]$/.exec(key);
|
|
4719
|
+
if (!match)
|
|
4720
|
+
continue;
|
|
4721
|
+
const [, fieldName, operatorKey] = match;
|
|
4722
|
+
const field = fieldMap.get(fieldName);
|
|
4723
|
+
if (!field || !isFilterOperator(operatorKey)) {
|
|
4724
|
+
invalidFilters.push(key);
|
|
4725
|
+
continue;
|
|
4726
|
+
}
|
|
4727
|
+
filters.push({ field, operator: operatorKey, rawValue, rawKey: key });
|
|
4728
|
+
}
|
|
4729
|
+
return { filters, invalidFilters };
|
|
4668
4730
|
}
|
|
4669
4731
|
function parseFieldSelection(query, ct) {
|
|
4670
4732
|
const includeFields = [...parseCsvParam(query.fields), ...parseCsvParam(query.select)];
|
|
@@ -4998,9 +5060,15 @@ var listPublicEntries = async (req, res) => {
|
|
|
4998
5060
|
const locale = req.query.locale ? String(req.query.locale) : void 0;
|
|
4999
5061
|
const fallbacks = req.query.fallback ? String(req.query.fallback).split(",") : [];
|
|
5000
5062
|
const knownFields = new Set(ct.fields.map((f2) => f2.name));
|
|
5063
|
+
const fieldMap = new Map(ct.fields.map((field) => [field.name, field]));
|
|
5001
5064
|
const systemSortFields = /* @__PURE__ */ new Set(["created_at", "updated_at", "published_at"]);
|
|
5002
5065
|
const filterClauses = [];
|
|
5003
5066
|
const filterValues = [];
|
|
5067
|
+
const { filters: parsedFilters, invalidFilters } = parseFilters(req.query, fieldMap);
|
|
5068
|
+
if (invalidFilters.length > 0) {
|
|
5069
|
+
res.status(400).json({ error: `Invalid filters: ${invalidFilters.join(", ")}` });
|
|
5070
|
+
return;
|
|
5071
|
+
}
|
|
5004
5072
|
const statusParam = String(req.query.status ?? "published");
|
|
5005
5073
|
if (statusParam === "published" || statusParam === "draft") {
|
|
5006
5074
|
filterClauses.push(`e.status = $${filterValues.length + 1}`);
|
|
@@ -5010,14 +5078,31 @@ var listPublicEntries = async (req, res) => {
|
|
|
5010
5078
|
const sortField = knownFields.has(rawSort) || systemSortFields.has(rawSort) ? rawSort : "created_at";
|
|
5011
5079
|
assertSafeIdentifier(sortField);
|
|
5012
5080
|
const sortDir = String(req.query.order ?? "desc").toLowerCase() === "asc" ? "ASC" : "DESC";
|
|
5013
|
-
for (const
|
|
5014
|
-
|
|
5081
|
+
for (const parsedFilter of parsedFilters) {
|
|
5082
|
+
const fieldName = parsedFilter.field.name;
|
|
5083
|
+
assertSafeIdentifier(fieldName);
|
|
5084
|
+
if (parsedFilter.operator === "eq" || parsedFilter.operator === "ne") {
|
|
5085
|
+
const rawValue = Array.isArray(parsedFilter.rawValue) ? parsedFilter.rawValue[0] : parsedFilter.rawValue;
|
|
5086
|
+
const coercedValue = typeof rawValue === "string" ? coerceFilterValue(rawValue, parsedFilter.field) : rawValue;
|
|
5087
|
+
filterClauses.push(`e.${fieldName} ${parsedFilter.operator === "ne" ? "!=" : "="} $${filterValues.length + 1}`);
|
|
5088
|
+
filterValues.push(coercedValue);
|
|
5015
5089
|
continue;
|
|
5016
|
-
if (knownFields.has(key)) {
|
|
5017
|
-
assertSafeIdentifier(key);
|
|
5018
|
-
filterClauses.push(`e.${key} = $${filterValues.length + 1}`);
|
|
5019
|
-
filterValues.push(value);
|
|
5020
5090
|
}
|
|
5091
|
+
const coercedValues = coerceFilterValues(parsedFilter.rawValue, parsedFilter.field);
|
|
5092
|
+
if (coercedValues.length === 0) {
|
|
5093
|
+
res.status(400).json({ error: `Filter "${parsedFilter.rawKey}" requires at least one value` });
|
|
5094
|
+
return;
|
|
5095
|
+
}
|
|
5096
|
+
filterClauses.push(parsedFilter.operator === "nin" ? `NOT (e.${fieldName} = ANY($${filterValues.length + 1}))` : `e.${fieldName} = ANY($${filterValues.length + 1})`);
|
|
5097
|
+
filterValues.push(coercedValues);
|
|
5098
|
+
}
|
|
5099
|
+
for (const [key, value] of Object.entries(req.query)) {
|
|
5100
|
+
if (key === "page" || key === "limit" || key === "status" || key === "sort" || key === "order" || key === "locale" || key === "fallback" || key === "fields" || key === "select" || key === "exclude" || key === "filters" || key.startsWith("filters["))
|
|
5101
|
+
continue;
|
|
5102
|
+
if (knownFields.has(key))
|
|
5103
|
+
continue;
|
|
5104
|
+
if (/_((?:n)?in|ne)$/.test(key))
|
|
5105
|
+
continue;
|
|
5021
5106
|
}
|
|
5022
5107
|
const where = filterClauses.length > 0 ? `WHERE ${filterClauses.join(" AND ")}` : "";
|
|
5023
5108
|
const limitParam = filterValues.length + 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plank-cms/plank",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Self-hosted headless CMS. Deploy in minutes on your own infrastructure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/fs-extra": "^11.0.4",
|
|
57
57
|
"tsup": "^8.5.0",
|
|
58
|
-
"@plank-cms/
|
|
59
|
-
"@plank-cms/
|
|
60
|
-
"@plank-cms/schema": "0.
|
|
58
|
+
"@plank-cms/core": "0.19.0",
|
|
59
|
+
"@plank-cms/db": "0.19.0",
|
|
60
|
+
"@plank-cms/schema": "0.19.0"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "tsup",
|