@dyrected/core 2.9.0 → 2.10.1
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/{app-config-gEQJFvnN.d.cts → app-config-BLC7WoyL.d.cts} +439 -44
- package/dist/{app-config-gEQJFvnN.d.ts → app-config-BLC7WoyL.d.ts} +439 -44
- package/dist/{chunk-XRIONGRY.js → chunk-H7DVVW2P.js} +115 -30
- package/dist/index.cjs +526 -67
- package/dist/index.d.cts +401 -86
- package/dist/index.d.ts +401 -86
- package/dist/index.js +408 -38
- package/dist/server.cjs +160 -39
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +45 -10
- package/dist/{where-sanitizer-3MNFMYA2.js → where-sanitizer-47QPJQ6O.js} +3 -2
- package/package.json +1 -1
package/dist/server.cjs
CHANGED
|
@@ -2318,11 +2318,12 @@ function sanitizeWhereClause(where, fields) {
|
|
|
2318
2318
|
function walk(node) {
|
|
2319
2319
|
const result = {};
|
|
2320
2320
|
for (const [key, value] of Object.entries(node)) {
|
|
2321
|
-
|
|
2321
|
+
const upperKey = key.toUpperCase();
|
|
2322
|
+
if (upperKey === "AND" || upperKey === "OR") {
|
|
2322
2323
|
if (Array.isArray(value)) {
|
|
2323
2324
|
const processed = value.map((v) => walk(v)).filter((v) => Object.keys(v).length > 0);
|
|
2324
2325
|
if (processed.length > 0) {
|
|
2325
|
-
result[
|
|
2326
|
+
result[upperKey] = processed;
|
|
2326
2327
|
}
|
|
2327
2328
|
}
|
|
2328
2329
|
continue;
|
|
@@ -3144,6 +3145,11 @@ function registerJexlHelpers(jexlInstance = import_jexl2.default) {
|
|
|
3144
3145
|
try {
|
|
3145
3146
|
jexlInstance.addBinaryOp("and", 20, (left, right) => Boolean(left && right));
|
|
3146
3147
|
jexlInstance.addBinaryOp("or", 10, (left, right) => Boolean(left || right));
|
|
3148
|
+
jexlInstance.addBinaryOp("in", 30, (left, right) => {
|
|
3149
|
+
if (Array.isArray(right)) return right.includes(left);
|
|
3150
|
+
if (typeof right === "string") return right.includes(String(left ?? ""));
|
|
3151
|
+
return false;
|
|
3152
|
+
});
|
|
3147
3153
|
} catch {
|
|
3148
3154
|
}
|
|
3149
3155
|
}
|
|
@@ -4036,6 +4042,17 @@ function validateUpload(file, config) {
|
|
|
4036
4042
|
}
|
|
4037
4043
|
return null;
|
|
4038
4044
|
}
|
|
4045
|
+
function generateUniqueUploadFilename(filename) {
|
|
4046
|
+
const lastDotIndex = filename.lastIndexOf(".");
|
|
4047
|
+
const ext = lastDotIndex !== -1 ? filename.substring(lastDotIndex).toLowerCase() : "";
|
|
4048
|
+
const baseName = lastDotIndex !== -1 ? filename.substring(0, lastDotIndex) : filename;
|
|
4049
|
+
const sanitizedBase = baseName.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "file";
|
|
4050
|
+
if (/-\d{10,13}-[a-z0-9]{4,10}$/.test(sanitizedBase)) {
|
|
4051
|
+
return `${sanitizedBase}${ext}`;
|
|
4052
|
+
}
|
|
4053
|
+
const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
4054
|
+
return `${sanitizedBase}-${uniqueSuffix}${ext}`;
|
|
4055
|
+
}
|
|
4039
4056
|
|
|
4040
4057
|
// src/auth/jexl.ts
|
|
4041
4058
|
var import_jexl5 = __toESM(require_Jexl(), 1);
|
|
@@ -5366,6 +5383,7 @@ var CollectionController = class {
|
|
|
5366
5383
|
createdBy: user?.sub ?? null,
|
|
5367
5384
|
updatedBy: user?.sub ?? null
|
|
5368
5385
|
};
|
|
5386
|
+
data = DefaultsService.apply(this.collection.fields, data);
|
|
5369
5387
|
if (this.collection.workflow) {
|
|
5370
5388
|
data = initializeWorkflowDocument(data, this.collection.workflow);
|
|
5371
5389
|
}
|
|
@@ -5487,8 +5505,9 @@ var CollectionController = class {
|
|
|
5487
5505
|
const siteId = c.get("siteId");
|
|
5488
5506
|
const workspaceId = c.get("workspaceId");
|
|
5489
5507
|
const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId;
|
|
5508
|
+
const uniqueFilename = generateUniqueUploadFilename(file.name);
|
|
5490
5509
|
const fileData = await storage.upload({
|
|
5491
|
-
filename:
|
|
5510
|
+
filename: uniqueFilename,
|
|
5492
5511
|
buffer,
|
|
5493
5512
|
mimeType: file.type,
|
|
5494
5513
|
prefix
|
|
@@ -6247,9 +6266,23 @@ var CollectionController = class {
|
|
|
6247
6266
|
400
|
|
6248
6267
|
);
|
|
6249
6268
|
}
|
|
6269
|
+
let rawAggregates;
|
|
6270
|
+
let groupBy;
|
|
6271
|
+
if ("aggregates" in body && typeof body.aggregates === "object" && body.aggregates !== null && !Array.isArray(body.aggregates)) {
|
|
6272
|
+
rawAggregates = body.aggregates;
|
|
6273
|
+
if (typeof body.groupBy === "string" && body.groupBy.trim()) {
|
|
6274
|
+
groupBy = body.groupBy.trim();
|
|
6275
|
+
}
|
|
6276
|
+
} else {
|
|
6277
|
+
rawAggregates = body;
|
|
6278
|
+
const queryGroupBy = c.req.query("groupBy");
|
|
6279
|
+
if (typeof queryGroupBy === "string" && queryGroupBy.trim()) {
|
|
6280
|
+
groupBy = queryGroupBy.trim();
|
|
6281
|
+
}
|
|
6282
|
+
}
|
|
6250
6283
|
const { sanitizeWhereClause: sanitizeWhereClause2 } = await Promise.resolve().then(() => (init_where_sanitizer(), where_sanitizer_exports));
|
|
6251
6284
|
const sanitizedAggregates = {};
|
|
6252
|
-
for (const [key, op] of Object.entries(
|
|
6285
|
+
for (const [key, op] of Object.entries(rawAggregates)) {
|
|
6253
6286
|
if (!op || typeof op !== "object" || Array.isArray(op)) {
|
|
6254
6287
|
return c.json(
|
|
6255
6288
|
{
|
|
@@ -6274,7 +6307,8 @@ var CollectionController = class {
|
|
|
6274
6307
|
}
|
|
6275
6308
|
const result = await db.aggregate({
|
|
6276
6309
|
collection: this.collection.slug,
|
|
6277
|
-
aggregates: sanitizedAggregates
|
|
6310
|
+
aggregates: sanitizedAggregates,
|
|
6311
|
+
...groupBy ? { groupBy } : {}
|
|
6278
6312
|
});
|
|
6279
6313
|
return c.json(result);
|
|
6280
6314
|
}
|
|
@@ -6670,6 +6704,7 @@ var MediaController = class {
|
|
|
6670
6704
|
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
6671
6705
|
const workspaceId = c.get("workspaceId");
|
|
6672
6706
|
const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId || "default";
|
|
6707
|
+
const uniqueFilename = generateUniqueUploadFilename(file.name);
|
|
6673
6708
|
let imageMetadata = {};
|
|
6674
6709
|
let imageSizes = {};
|
|
6675
6710
|
if (imageService && file.type.startsWith("image/")) {
|
|
@@ -6692,7 +6727,7 @@ var MediaController = class {
|
|
|
6692
6727
|
}
|
|
6693
6728
|
}
|
|
6694
6729
|
const fileData = await storage.upload({
|
|
6695
|
-
filename:
|
|
6730
|
+
filename: uniqueFilename,
|
|
6696
6731
|
buffer,
|
|
6697
6732
|
mimeType: file.type,
|
|
6698
6733
|
prefix
|
|
@@ -6705,9 +6740,10 @@ var MediaController = class {
|
|
|
6705
6740
|
};
|
|
6706
6741
|
if (imageSizes) {
|
|
6707
6742
|
for (const [sizeName, sizeData] of Object.entries(imageSizes)) {
|
|
6708
|
-
const
|
|
6709
|
-
const
|
|
6710
|
-
const
|
|
6743
|
+
const lastDot = uniqueFilename.lastIndexOf(".");
|
|
6744
|
+
const ext = lastDot !== -1 ? uniqueFilename.substring(lastDot) : "";
|
|
6745
|
+
const baseName = lastDot !== -1 ? uniqueFilename.substring(0, lastDot) : uniqueFilename;
|
|
6746
|
+
const sizeFilename = `${baseName}-${sizeName}${ext}`;
|
|
6711
6747
|
try {
|
|
6712
6748
|
const sizeFileData = await storage.upload({
|
|
6713
6749
|
filename: sizeFilename,
|
|
@@ -8959,39 +8995,94 @@ function generateOpenApi(config) {
|
|
|
8959
8995
|
post: {
|
|
8960
8996
|
tags: [collectionTag],
|
|
8961
8997
|
summary: `Aggregate ${labels.plural}`,
|
|
8962
|
-
description: `Compute statistical aggregations (count, sum, avg, min, max) across ${labels.plural}.`,
|
|
8998
|
+
description: `Compute statistical aggregations (count, countDistinct, distinct, sum, avg, min, max, groupBy) across ${labels.plural}.`,
|
|
8999
|
+
parameters: [
|
|
9000
|
+
{
|
|
9001
|
+
name: "groupBy",
|
|
9002
|
+
in: "query",
|
|
9003
|
+
description: "Optional field name to compute aggregates grouped by distinct values",
|
|
9004
|
+
required: false,
|
|
9005
|
+
schema: { type: "string" }
|
|
9006
|
+
}
|
|
9007
|
+
],
|
|
8963
9008
|
requestBody: {
|
|
8964
9009
|
required: true,
|
|
8965
9010
|
content: {
|
|
8966
9011
|
"application/json": {
|
|
8967
9012
|
schema: {
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
sum: { type: "string", description: "Field to sum" },
|
|
8974
|
-
avg: { type: "string", description: "Field to average" },
|
|
8975
|
-
min: { type: "string", description: "Field for minimum" },
|
|
8976
|
-
max: { type: "string", description: "Field for maximum" },
|
|
8977
|
-
cast: {
|
|
8978
|
-
type: "string",
|
|
8979
|
-
enum: [
|
|
8980
|
-
"number",
|
|
8981
|
-
"integer",
|
|
8982
|
-
"float",
|
|
8983
|
-
"boolean",
|
|
8984
|
-
"date",
|
|
8985
|
-
"string"
|
|
8986
|
-
]
|
|
8987
|
-
},
|
|
8988
|
-
where: {
|
|
9013
|
+
oneOf: [
|
|
9014
|
+
{
|
|
9015
|
+
type: "object",
|
|
9016
|
+
description: "Flat aggregate operations dictionary",
|
|
9017
|
+
additionalProperties: {
|
|
8989
9018
|
type: "object",
|
|
8990
|
-
|
|
8991
|
-
|
|
9019
|
+
properties: {
|
|
9020
|
+
count: { type: "string", example: "*" },
|
|
9021
|
+
countDistinct: { type: "string", description: "Field to count unique non-null values" },
|
|
9022
|
+
distinct: { type: "string", description: "Field to extract unique non-null values" },
|
|
9023
|
+
sum: { type: "string", description: "Field to sum" },
|
|
9024
|
+
avg: { type: "string", description: "Field to average" },
|
|
9025
|
+
min: { type: "string", description: "Field for minimum" },
|
|
9026
|
+
max: { type: "string", description: "Field for maximum" },
|
|
9027
|
+
cast: {
|
|
9028
|
+
type: "string",
|
|
9029
|
+
enum: [
|
|
9030
|
+
"number",
|
|
9031
|
+
"integer",
|
|
9032
|
+
"float",
|
|
9033
|
+
"boolean",
|
|
9034
|
+
"date",
|
|
9035
|
+
"string"
|
|
9036
|
+
]
|
|
9037
|
+
},
|
|
9038
|
+
where: {
|
|
9039
|
+
type: "object",
|
|
9040
|
+
description: "Per-aggregate filter condition",
|
|
9041
|
+
additionalProperties: true
|
|
9042
|
+
}
|
|
9043
|
+
}
|
|
8992
9044
|
}
|
|
9045
|
+
},
|
|
9046
|
+
{
|
|
9047
|
+
type: "object",
|
|
9048
|
+
description: "Grouped aggregate payload with explicit groupBy property",
|
|
9049
|
+
properties: {
|
|
9050
|
+
groupBy: { type: "string", description: "Field name to group aggregates by" },
|
|
9051
|
+
aggregates: {
|
|
9052
|
+
type: "object",
|
|
9053
|
+
additionalProperties: {
|
|
9054
|
+
type: "object",
|
|
9055
|
+
properties: {
|
|
9056
|
+
count: { type: "string", example: "*" },
|
|
9057
|
+
countDistinct: { type: "string", description: "Field to count unique non-null values" },
|
|
9058
|
+
distinct: { type: "string", description: "Field to extract unique non-null values" },
|
|
9059
|
+
sum: { type: "string", description: "Field to sum" },
|
|
9060
|
+
avg: { type: "string", description: "Field to average" },
|
|
9061
|
+
min: { type: "string", description: "Field for minimum" },
|
|
9062
|
+
max: { type: "string", description: "Field for maximum" },
|
|
9063
|
+
cast: {
|
|
9064
|
+
type: "string",
|
|
9065
|
+
enum: [
|
|
9066
|
+
"number",
|
|
9067
|
+
"integer",
|
|
9068
|
+
"float",
|
|
9069
|
+
"boolean",
|
|
9070
|
+
"date",
|
|
9071
|
+
"string"
|
|
9072
|
+
]
|
|
9073
|
+
},
|
|
9074
|
+
where: {
|
|
9075
|
+
type: "object",
|
|
9076
|
+
description: "Per-aggregate filter condition",
|
|
9077
|
+
additionalProperties: true
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
}
|
|
9082
|
+
},
|
|
9083
|
+
required: ["aggregates"]
|
|
8993
9084
|
}
|
|
8994
|
-
|
|
9085
|
+
]
|
|
8995
9086
|
}
|
|
8996
9087
|
}
|
|
8997
9088
|
}
|
|
@@ -9002,11 +9093,36 @@ function generateOpenApi(config) {
|
|
|
9002
9093
|
content: {
|
|
9003
9094
|
"application/json": {
|
|
9004
9095
|
schema: {
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9096
|
+
oneOf: [
|
|
9097
|
+
{
|
|
9098
|
+
type: "object",
|
|
9099
|
+
description: "Flat aggregate results map",
|
|
9100
|
+
additionalProperties: {
|
|
9101
|
+
oneOf: [
|
|
9102
|
+
{ type: "number", nullable: true },
|
|
9103
|
+
{ type: "array", items: { type: "string" } }
|
|
9104
|
+
]
|
|
9105
|
+
}
|
|
9106
|
+
},
|
|
9107
|
+
{
|
|
9108
|
+
type: "object",
|
|
9109
|
+
description: "Grouped aggregate results breakdown",
|
|
9110
|
+
properties: {
|
|
9111
|
+
groups: {
|
|
9112
|
+
type: "object",
|
|
9113
|
+
additionalProperties: {
|
|
9114
|
+
type: "object",
|
|
9115
|
+
additionalProperties: {
|
|
9116
|
+
oneOf: [
|
|
9117
|
+
{ type: "number", nullable: true },
|
|
9118
|
+
{ type: "array", items: { type: "string" } }
|
|
9119
|
+
]
|
|
9120
|
+
}
|
|
9121
|
+
}
|
|
9122
|
+
}
|
|
9123
|
+
}
|
|
9124
|
+
}
|
|
9125
|
+
]
|
|
9010
9126
|
}
|
|
9011
9127
|
}
|
|
9012
9128
|
}
|
|
@@ -9753,6 +9869,9 @@ async function serializeViewForApi(view, serializeAccess) {
|
|
|
9753
9869
|
columns: view.columns,
|
|
9754
9870
|
sort: view.sort,
|
|
9755
9871
|
metrics: view.metrics,
|
|
9872
|
+
features: view.features,
|
|
9873
|
+
actionOrder: view.actionOrder,
|
|
9874
|
+
components: view.components,
|
|
9756
9875
|
actions: await Promise.all(
|
|
9757
9876
|
(view.actions || []).map(async (action) => ({
|
|
9758
9877
|
name: action.name,
|
|
@@ -9760,6 +9879,7 @@ async function serializeViewForApi(view, serializeAccess) {
|
|
|
9760
9879
|
icon: action.icon,
|
|
9761
9880
|
type: action.type ?? "row",
|
|
9762
9881
|
confirm: action.confirm,
|
|
9882
|
+
submitLabel: action.submitLabel,
|
|
9763
9883
|
fields: action.fields?.map(serializeFieldForApi),
|
|
9764
9884
|
mutation: action.mutation,
|
|
9765
9885
|
// Self-hosted handlers are intentionally omitted: they never leave the server.
|
|
@@ -9829,6 +9949,7 @@ function registerRoutes(app, config) {
|
|
|
9829
9949
|
auth: !!col.auth,
|
|
9830
9950
|
audit: !!col.audit,
|
|
9831
9951
|
drafts: !!col.drafts,
|
|
9952
|
+
defaultView: col.defaultView ?? col.admin?.defaultView,
|
|
9832
9953
|
views: await Promise.all((col.views || []).map((view) => serializeViewForApi(view, serializeAccess))),
|
|
9833
9954
|
admin: col.admin,
|
|
9834
9955
|
detail: serializeDetailForApi(col.detail),
|
package/dist/server.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ import * as hono_types from 'hono/types';
|
|
|
2
2
|
import * as hono from 'hono';
|
|
3
3
|
import { Context, Hono } from 'hono';
|
|
4
4
|
import { Logger } from 'pino';
|
|
5
|
-
import { D as DyrectedConfig,
|
|
5
|
+
import { D as DyrectedConfig, b5 as DyrectedObservabilityConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-BLC7WoyL.cjs';
|
|
6
6
|
import { JWTPayload } from 'jose';
|
|
7
7
|
import { Tracer, Counter, Attributes, Histogram, Span } from '@opentelemetry/api';
|
|
8
8
|
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
|
@@ -498,7 +498,7 @@ declare class CollectionController {
|
|
|
498
498
|
}, 403, "json">) | (Response & hono.TypedResponse<{
|
|
499
499
|
message: string;
|
|
500
500
|
}, 400, "json">) | (Response & hono.TypedResponse<{
|
|
501
|
-
[x: string]:
|
|
501
|
+
[x: string]: any;
|
|
502
502
|
}, hono_utils_http_status.ContentfulStatusCode, "json">)>;
|
|
503
503
|
}
|
|
504
504
|
|
package/dist/server.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as hono_types from 'hono/types';
|
|
|
2
2
|
import * as hono from 'hono';
|
|
3
3
|
import { Context, Hono } from 'hono';
|
|
4
4
|
import { Logger } from 'pino';
|
|
5
|
-
import { D as DyrectedConfig,
|
|
5
|
+
import { D as DyrectedConfig, b5 as DyrectedObservabilityConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-BLC7WoyL.js';
|
|
6
6
|
import { JWTPayload } from 'jose';
|
|
7
7
|
import { Tracer, Counter, Attributes, Histogram, Span } from '@opentelemetry/api';
|
|
8
8
|
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
|
@@ -498,7 +498,7 @@ declare class CollectionController {
|
|
|
498
498
|
}, 403, "json">) | (Response & hono.TypedResponse<{
|
|
499
499
|
message: string;
|
|
500
500
|
}, 400, "json">) | (Response & hono.TypedResponse<{
|
|
501
|
-
[x: string]:
|
|
501
|
+
[x: string]: any;
|
|
502
502
|
}, hono_utils_http_status.ContentfulStatusCode, "json">)>;
|
|
503
503
|
}
|
|
504
504
|
|
package/dist/server.js
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
touchAuthSession,
|
|
31
31
|
transitionWorkflow,
|
|
32
32
|
verifyCollectionToken
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-H7DVVW2P.js";
|
|
34
34
|
import {
|
|
35
35
|
attachRequestMetrics,
|
|
36
36
|
bindObservabilityRuntime,
|
|
@@ -436,6 +436,17 @@ function validateUpload(file, config) {
|
|
|
436
436
|
}
|
|
437
437
|
return null;
|
|
438
438
|
}
|
|
439
|
+
function generateUniqueUploadFilename(filename) {
|
|
440
|
+
const lastDotIndex = filename.lastIndexOf(".");
|
|
441
|
+
const ext = lastDotIndex !== -1 ? filename.substring(lastDotIndex).toLowerCase() : "";
|
|
442
|
+
const baseName = lastDotIndex !== -1 ? filename.substring(0, lastDotIndex) : filename;
|
|
443
|
+
const sanitizedBase = baseName.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "file";
|
|
444
|
+
if (/-\d{10,13}-[a-z0-9]{4,10}$/.test(sanitizedBase)) {
|
|
445
|
+
return `${sanitizedBase}${ext}`;
|
|
446
|
+
}
|
|
447
|
+
const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
448
|
+
return `${sanitizedBase}-${uniqueSuffix}${ext}`;
|
|
449
|
+
}
|
|
439
450
|
|
|
440
451
|
// src/auth/jexl.ts
|
|
441
452
|
var import_jexl2 = __toESM(require_Jexl(), 1);
|
|
@@ -1021,7 +1032,7 @@ var CollectionController = class {
|
|
|
1021
1032
|
if (this.collection.admin?.filterable === false) {
|
|
1022
1033
|
where = void 0;
|
|
1023
1034
|
} else {
|
|
1024
|
-
const { sanitizeWhereClause } = await import("./where-sanitizer-
|
|
1035
|
+
const { sanitizeWhereClause } = await import("./where-sanitizer-47QPJQ6O.js");
|
|
1025
1036
|
where = sanitizeWhereClause(where, this.collection.fields);
|
|
1026
1037
|
if (Object.keys(where).length === 0) {
|
|
1027
1038
|
where = void 0;
|
|
@@ -1319,6 +1330,7 @@ var CollectionController = class {
|
|
|
1319
1330
|
createdBy: user?.sub ?? null,
|
|
1320
1331
|
updatedBy: user?.sub ?? null
|
|
1321
1332
|
};
|
|
1333
|
+
data = DefaultsService.apply(this.collection.fields, data);
|
|
1322
1334
|
if (this.collection.workflow) {
|
|
1323
1335
|
data = initializeWorkflowDocument(data, this.collection.workflow);
|
|
1324
1336
|
}
|
|
@@ -1440,8 +1452,9 @@ var CollectionController = class {
|
|
|
1440
1452
|
const siteId = c.get("siteId");
|
|
1441
1453
|
const workspaceId = c.get("workspaceId");
|
|
1442
1454
|
const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId;
|
|
1455
|
+
const uniqueFilename = generateUniqueUploadFilename(file.name);
|
|
1443
1456
|
const fileData = await storage.upload({
|
|
1444
|
-
filename:
|
|
1457
|
+
filename: uniqueFilename,
|
|
1445
1458
|
buffer,
|
|
1446
1459
|
mimeType: file.type,
|
|
1447
1460
|
prefix
|
|
@@ -2200,9 +2213,23 @@ var CollectionController = class {
|
|
|
2200
2213
|
400
|
|
2201
2214
|
);
|
|
2202
2215
|
}
|
|
2203
|
-
|
|
2216
|
+
let rawAggregates;
|
|
2217
|
+
let groupBy;
|
|
2218
|
+
if ("aggregates" in body && typeof body.aggregates === "object" && body.aggregates !== null && !Array.isArray(body.aggregates)) {
|
|
2219
|
+
rawAggregates = body.aggregates;
|
|
2220
|
+
if (typeof body.groupBy === "string" && body.groupBy.trim()) {
|
|
2221
|
+
groupBy = body.groupBy.trim();
|
|
2222
|
+
}
|
|
2223
|
+
} else {
|
|
2224
|
+
rawAggregates = body;
|
|
2225
|
+
const queryGroupBy = c.req.query("groupBy");
|
|
2226
|
+
if (typeof queryGroupBy === "string" && queryGroupBy.trim()) {
|
|
2227
|
+
groupBy = queryGroupBy.trim();
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
const { sanitizeWhereClause } = await import("./where-sanitizer-47QPJQ6O.js");
|
|
2204
2231
|
const sanitizedAggregates = {};
|
|
2205
|
-
for (const [key, op] of Object.entries(
|
|
2232
|
+
for (const [key, op] of Object.entries(rawAggregates)) {
|
|
2206
2233
|
if (!op || typeof op !== "object" || Array.isArray(op)) {
|
|
2207
2234
|
return c.json(
|
|
2208
2235
|
{
|
|
@@ -2227,7 +2254,8 @@ var CollectionController = class {
|
|
|
2227
2254
|
}
|
|
2228
2255
|
const result = await db.aggregate({
|
|
2229
2256
|
collection: this.collection.slug,
|
|
2230
|
-
aggregates: sanitizedAggregates
|
|
2257
|
+
aggregates: sanitizedAggregates,
|
|
2258
|
+
...groupBy ? { groupBy } : {}
|
|
2231
2259
|
});
|
|
2232
2260
|
return c.json(result);
|
|
2233
2261
|
}
|
|
@@ -2509,6 +2537,7 @@ var MediaController = class {
|
|
|
2509
2537
|
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
2510
2538
|
const workspaceId = c.get("workspaceId");
|
|
2511
2539
|
const prefix = workspaceId ? `${workspaceId}/${siteId}` : siteId || "default";
|
|
2540
|
+
const uniqueFilename = generateUniqueUploadFilename(file.name);
|
|
2512
2541
|
let imageMetadata = {};
|
|
2513
2542
|
let imageSizes = {};
|
|
2514
2543
|
if (imageService && file.type.startsWith("image/")) {
|
|
@@ -2531,7 +2560,7 @@ var MediaController = class {
|
|
|
2531
2560
|
}
|
|
2532
2561
|
}
|
|
2533
2562
|
const fileData = await storage.upload({
|
|
2534
|
-
filename:
|
|
2563
|
+
filename: uniqueFilename,
|
|
2535
2564
|
buffer,
|
|
2536
2565
|
mimeType: file.type,
|
|
2537
2566
|
prefix
|
|
@@ -2544,9 +2573,10 @@ var MediaController = class {
|
|
|
2544
2573
|
};
|
|
2545
2574
|
if (imageSizes) {
|
|
2546
2575
|
for (const [sizeName, sizeData] of Object.entries(imageSizes)) {
|
|
2547
|
-
const
|
|
2548
|
-
const
|
|
2549
|
-
const
|
|
2576
|
+
const lastDot = uniqueFilename.lastIndexOf(".");
|
|
2577
|
+
const ext = lastDot !== -1 ? uniqueFilename.substring(lastDot) : "";
|
|
2578
|
+
const baseName = lastDot !== -1 ? uniqueFilename.substring(0, lastDot) : uniqueFilename;
|
|
2579
|
+
const sizeFilename = `${baseName}-${sizeName}${ext}`;
|
|
2550
2580
|
try {
|
|
2551
2581
|
const sizeFileData = await storage.upload({
|
|
2552
2582
|
filename: sizeFilename,
|
|
@@ -4564,6 +4594,9 @@ async function serializeViewForApi(view, serializeAccess) {
|
|
|
4564
4594
|
columns: view.columns,
|
|
4565
4595
|
sort: view.sort,
|
|
4566
4596
|
metrics: view.metrics,
|
|
4597
|
+
features: view.features,
|
|
4598
|
+
actionOrder: view.actionOrder,
|
|
4599
|
+
components: view.components,
|
|
4567
4600
|
actions: await Promise.all(
|
|
4568
4601
|
(view.actions || []).map(async (action) => ({
|
|
4569
4602
|
name: action.name,
|
|
@@ -4571,6 +4604,7 @@ async function serializeViewForApi(view, serializeAccess) {
|
|
|
4571
4604
|
icon: action.icon,
|
|
4572
4605
|
type: action.type ?? "row",
|
|
4573
4606
|
confirm: action.confirm,
|
|
4607
|
+
submitLabel: action.submitLabel,
|
|
4574
4608
|
fields: action.fields?.map(serializeFieldForApi),
|
|
4575
4609
|
mutation: action.mutation,
|
|
4576
4610
|
// Self-hosted handlers are intentionally omitted: they never leave the server.
|
|
@@ -4640,6 +4674,7 @@ function registerRoutes(app, config) {
|
|
|
4640
4674
|
auth: !!col.auth,
|
|
4641
4675
|
audit: !!col.audit,
|
|
4642
4676
|
drafts: !!col.drafts,
|
|
4677
|
+
defaultView: col.defaultView ?? col.admin?.defaultView,
|
|
4643
4678
|
views: await Promise.all((col.views || []).map((view) => serializeViewForApi(view, serializeAccess))),
|
|
4644
4679
|
admin: col.admin,
|
|
4645
4680
|
detail: serializeDetailForApi(col.detail),
|
|
@@ -28,11 +28,12 @@ function sanitizeWhereClause(where, fields) {
|
|
|
28
28
|
function walk(node) {
|
|
29
29
|
const result = {};
|
|
30
30
|
for (const [key, value] of Object.entries(node)) {
|
|
31
|
-
|
|
31
|
+
const upperKey = key.toUpperCase();
|
|
32
|
+
if (upperKey === "AND" || upperKey === "OR") {
|
|
32
33
|
if (Array.isArray(value)) {
|
|
33
34
|
const processed = value.map((v) => walk(v)).filter((v) => Object.keys(v).length > 0);
|
|
34
35
|
if (processed.length > 0) {
|
|
35
|
-
result[
|
|
36
|
+
result[upperKey] = processed;
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
continue;
|