@dyrected/core 2.5.64 → 2.6.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/app-config-DbK2Tv6i.d.cts +2080 -0
- package/dist/app-config-DbK2Tv6i.d.ts +2080 -0
- package/dist/app-config-Dms6kvE-.d.cts +2072 -0
- package/dist/app-config-Dms6kvE-.d.ts +2072 -0
- package/dist/app-config-x-CnipcA.d.cts +2072 -0
- package/dist/app-config-x-CnipcA.d.ts +2072 -0
- package/dist/chunk-K3ASMUKV.js +2601 -0
- package/dist/chunk-ROECUARS.js +2615 -0
- package/dist/index.cjs +6 -14
- package/dist/index.d.cts +193 -21
- package/dist/index.d.ts +193 -21
- package/dist/index.js +1 -1
- package/dist/server.cjs +214 -22
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +209 -9
- package/package.json +1 -1
package/dist/server.cjs
CHANGED
|
@@ -177,7 +177,7 @@ var PopulationService = class {
|
|
|
177
177
|
}
|
|
178
178
|
const populatedDoc = { ...data };
|
|
179
179
|
for (const field of fields) {
|
|
180
|
-
if (field.type === "join" && field.collection && field.on && currentDepth
|
|
180
|
+
if (field.type === "join" && field.collection && field.on && currentDepth < maxDepth) {
|
|
181
181
|
const targetCollection = this.collections.find(
|
|
182
182
|
(c) => c.slug === field.collection
|
|
183
183
|
);
|
|
@@ -196,7 +196,7 @@ var PopulationService = class {
|
|
|
196
196
|
(doc) => DefaultsService.apply(targetCollection.fields, doc)
|
|
197
197
|
),
|
|
198
198
|
fields: targetCollection.fields,
|
|
199
|
-
currentDepth: 1,
|
|
199
|
+
currentDepth: currentDepth + 1,
|
|
200
200
|
maxDepth
|
|
201
201
|
});
|
|
202
202
|
populatedDoc[field.name] = {
|
|
@@ -1387,6 +1387,191 @@ function humanizeProviderName(id, type) {
|
|
|
1387
1387
|
return cleaned.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1388
1388
|
}
|
|
1389
1389
|
|
|
1390
|
+
// src/utils/collection-search.ts
|
|
1391
|
+
var DEFAULT_SEARCHABLE_NAMES = /* @__PURE__ */ new Set([
|
|
1392
|
+
"title",
|
|
1393
|
+
"name",
|
|
1394
|
+
"label",
|
|
1395
|
+
"slug",
|
|
1396
|
+
"email",
|
|
1397
|
+
"caption",
|
|
1398
|
+
"description"
|
|
1399
|
+
]);
|
|
1400
|
+
var MAX_RELATION_MATCHES = 50;
|
|
1401
|
+
var MAX_RELATION_TITLE_DEPTH = 8;
|
|
1402
|
+
var TITLE_FALLBACK_NAMES = ["title", "name", "label", "heading", "email", "subject"];
|
|
1403
|
+
function isScalarSearchField(field) {
|
|
1404
|
+
return field.type === "text" || field.type === "textarea" || field.type === "richText" || field.type === "email";
|
|
1405
|
+
}
|
|
1406
|
+
function findFieldByName(collection, fieldName) {
|
|
1407
|
+
if (!fieldName) return void 0;
|
|
1408
|
+
return collection.fields.find((field) => field.name === fieldName);
|
|
1409
|
+
}
|
|
1410
|
+
function resolveCollectionTitleField(collection) {
|
|
1411
|
+
const configured = findFieldByName(collection, collection.admin?.useAsTitle);
|
|
1412
|
+
if (configured) return configured;
|
|
1413
|
+
for (const name of TITLE_FALLBACK_NAMES) {
|
|
1414
|
+
const field = findFieldByName(collection, name);
|
|
1415
|
+
if (field) return field;
|
|
1416
|
+
}
|
|
1417
|
+
return collection.fields.find((field) => !!field.name && field.type !== "join" && field.type !== "row");
|
|
1418
|
+
}
|
|
1419
|
+
function getCollectionSearchableFields(collection) {
|
|
1420
|
+
const fieldByName = new Map(
|
|
1421
|
+
collection.fields.filter((field) => !!field.name).map((field) => [field.name, field])
|
|
1422
|
+
);
|
|
1423
|
+
const configured = collection.admin?.searchableFields?.map((fieldName) => fieldByName.get(fieldName)).filter((field) => !!field);
|
|
1424
|
+
if (configured && configured.length > 0) {
|
|
1425
|
+
return configured;
|
|
1426
|
+
}
|
|
1427
|
+
const results = [];
|
|
1428
|
+
const pushUnique = (field) => {
|
|
1429
|
+
if (!field?.name) return;
|
|
1430
|
+
if (results.some((item) => item.name === field.name)) return;
|
|
1431
|
+
results.push(field);
|
|
1432
|
+
};
|
|
1433
|
+
pushUnique(findFieldByName(collection, collection.admin?.useAsTitle));
|
|
1434
|
+
for (const field of collection.fields) {
|
|
1435
|
+
if (!field.name) continue;
|
|
1436
|
+
if (isScalarSearchField(field) && DEFAULT_SEARCHABLE_NAMES.has(field.name)) {
|
|
1437
|
+
pushUnique(field);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return results;
|
|
1441
|
+
}
|
|
1442
|
+
function buildScalarSearchClause(fieldName, search) {
|
|
1443
|
+
return { [fieldName]: { contains: search } };
|
|
1444
|
+
}
|
|
1445
|
+
function getFallbackTitleFields(collection) {
|
|
1446
|
+
const fields = [];
|
|
1447
|
+
for (const name of TITLE_FALLBACK_NAMES) {
|
|
1448
|
+
const field = findFieldByName(collection, name);
|
|
1449
|
+
if (!field?.name || !isScalarSearchField(field)) continue;
|
|
1450
|
+
if (fields.some((item) => item.name === field.name)) continue;
|
|
1451
|
+
fields.push(field);
|
|
1452
|
+
}
|
|
1453
|
+
return fields;
|
|
1454
|
+
}
|
|
1455
|
+
async function findMatchingDocumentIdsByWhere(args) {
|
|
1456
|
+
const { collection, where, db } = args;
|
|
1457
|
+
if (!where) return [];
|
|
1458
|
+
const matches = await db.find({
|
|
1459
|
+
collection: collection.slug,
|
|
1460
|
+
where,
|
|
1461
|
+
limit: MAX_RELATION_MATCHES
|
|
1462
|
+
});
|
|
1463
|
+
return matches.docs.map((doc) => doc?.id).filter((id) => typeof id === "string");
|
|
1464
|
+
}
|
|
1465
|
+
function buildMultiRelationshipClause(fieldName, ids) {
|
|
1466
|
+
const matches = ids.map((id) => ({
|
|
1467
|
+
OR: [
|
|
1468
|
+
{ [fieldName]: { contains: `"${id}"` } },
|
|
1469
|
+
{ [fieldName]: { contains: id } }
|
|
1470
|
+
]
|
|
1471
|
+
}));
|
|
1472
|
+
if (matches.length === 0) return void 0;
|
|
1473
|
+
return matches.length === 1 ? matches[0] : { OR: matches };
|
|
1474
|
+
}
|
|
1475
|
+
function buildRelationshipMatchClause(field, ids) {
|
|
1476
|
+
if (!field.name || ids.length === 0) return void 0;
|
|
1477
|
+
if (field.hasMany) {
|
|
1478
|
+
return buildMultiRelationshipClause(field.name, ids);
|
|
1479
|
+
}
|
|
1480
|
+
return { [field.name]: { in: ids } };
|
|
1481
|
+
}
|
|
1482
|
+
async function findMatchingDocumentIdsByTitle(args) {
|
|
1483
|
+
const { collection, search, db, collections, depth = 0 } = args;
|
|
1484
|
+
if (depth >= MAX_RELATION_TITLE_DEPTH) {
|
|
1485
|
+
return [];
|
|
1486
|
+
}
|
|
1487
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1488
|
+
const titleField = resolveCollectionTitleField(collection);
|
|
1489
|
+
if (titleField?.name && isScalarSearchField(titleField)) {
|
|
1490
|
+
for (const id of await findMatchingDocumentIdsByWhere({
|
|
1491
|
+
collection,
|
|
1492
|
+
where: buildScalarSearchClause(titleField.name, search),
|
|
1493
|
+
db
|
|
1494
|
+
})) {
|
|
1495
|
+
ids.add(id);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
if (titleField?.type === "relationship" && titleField.relationTo) {
|
|
1499
|
+
for (const field of getFallbackTitleFields(collection)) {
|
|
1500
|
+
for (const id of await findMatchingDocumentIdsByWhere({
|
|
1501
|
+
collection,
|
|
1502
|
+
where: buildScalarSearchClause(field.name, search),
|
|
1503
|
+
db
|
|
1504
|
+
})) {
|
|
1505
|
+
ids.add(id);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
const relatedCollection = collections.find(
|
|
1509
|
+
(item) => item.slug === titleField.relationTo
|
|
1510
|
+
);
|
|
1511
|
+
if (relatedCollection) {
|
|
1512
|
+
const relatedIds = await findMatchingDocumentIdsByTitle({
|
|
1513
|
+
collection: relatedCollection,
|
|
1514
|
+
search,
|
|
1515
|
+
db,
|
|
1516
|
+
collections,
|
|
1517
|
+
depth: depth + 1
|
|
1518
|
+
});
|
|
1519
|
+
const relationClause = buildRelationshipMatchClause(titleField, relatedIds);
|
|
1520
|
+
for (const id of await findMatchingDocumentIdsByWhere({
|
|
1521
|
+
collection,
|
|
1522
|
+
where: relationClause,
|
|
1523
|
+
db
|
|
1524
|
+
})) {
|
|
1525
|
+
ids.add(id);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return Array.from(ids).slice(0, MAX_RELATION_MATCHES);
|
|
1530
|
+
}
|
|
1531
|
+
async function buildRelationshipSearchClause(args) {
|
|
1532
|
+
const { field, search, db, collections } = args;
|
|
1533
|
+
if (!field.name || field.type !== "relationship" || !field.relationTo) {
|
|
1534
|
+
return void 0;
|
|
1535
|
+
}
|
|
1536
|
+
const relatedCollection = collections.find(
|
|
1537
|
+
(collection) => collection.slug === field.relationTo
|
|
1538
|
+
);
|
|
1539
|
+
if (!relatedCollection) return void 0;
|
|
1540
|
+
const ids = await findMatchingDocumentIdsByTitle({
|
|
1541
|
+
collection: relatedCollection,
|
|
1542
|
+
search,
|
|
1543
|
+
db,
|
|
1544
|
+
collections
|
|
1545
|
+
});
|
|
1546
|
+
return buildRelationshipMatchClause(field, ids);
|
|
1547
|
+
}
|
|
1548
|
+
async function buildCollectionSearchWhere(args) {
|
|
1549
|
+
const { collection, search, db, collections } = args;
|
|
1550
|
+
const trimmedSearch = search?.trim();
|
|
1551
|
+
if (!trimmedSearch) return void 0;
|
|
1552
|
+
const searchableFields = getCollectionSearchableFields(collection);
|
|
1553
|
+
if (searchableFields.length === 0) return void 0;
|
|
1554
|
+
const clauses = [];
|
|
1555
|
+
for (const field of searchableFields) {
|
|
1556
|
+
if (!field.name) continue;
|
|
1557
|
+
if (isScalarSearchField(field)) {
|
|
1558
|
+
clauses.push(buildScalarSearchClause(field.name, trimmedSearch));
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
if (field.type === "relationship") {
|
|
1562
|
+
const clause = await buildRelationshipSearchClause({
|
|
1563
|
+
field,
|
|
1564
|
+
search: trimmedSearch,
|
|
1565
|
+
db,
|
|
1566
|
+
collections
|
|
1567
|
+
});
|
|
1568
|
+
if (clause) clauses.push(clause);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
if (clauses.length === 0) return void 0;
|
|
1572
|
+
return clauses.length === 1 ? clauses[0] : { OR: clauses };
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1390
1575
|
// src/utils/access-control.ts
|
|
1391
1576
|
function toHookRequestContext(req) {
|
|
1392
1577
|
return {
|
|
@@ -1899,6 +2084,7 @@ var CollectionController = class {
|
|
|
1899
2084
|
const limit2 = Number(c.req.query("limit")) || 10;
|
|
1900
2085
|
const page2 = Number(c.req.query("page")) || 1;
|
|
1901
2086
|
const sort2 = c.req.query("sort") || void 0;
|
|
2087
|
+
const search2 = c.req.query("search") || void 0;
|
|
1902
2088
|
let where2 = void 0;
|
|
1903
2089
|
const whereRaw2 = c.req.query("where");
|
|
1904
2090
|
if (whereRaw2) {
|
|
@@ -1907,6 +2093,15 @@ var CollectionController = class {
|
|
|
1907
2093
|
} catch {
|
|
1908
2094
|
}
|
|
1909
2095
|
}
|
|
2096
|
+
const searchWhere2 = await buildCollectionSearchWhere({
|
|
2097
|
+
collection: this.collection,
|
|
2098
|
+
search: search2,
|
|
2099
|
+
db: createReadonlyDb(db),
|
|
2100
|
+
collections: config.collections
|
|
2101
|
+
});
|
|
2102
|
+
if (searchWhere2) {
|
|
2103
|
+
where2 = mergeWhereConstraint(where2, searchWhere2);
|
|
2104
|
+
}
|
|
1910
2105
|
const paginatedResult = await provider.members.list({
|
|
1911
2106
|
limit: limit2,
|
|
1912
2107
|
page: page2,
|
|
@@ -1941,6 +2136,7 @@ var CollectionController = class {
|
|
|
1941
2136
|
const page = Number(c.req.query("page")) || 1;
|
|
1942
2137
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
1943
2138
|
const sort = c.req.query("sort") || void 0;
|
|
2139
|
+
const search = c.req.query("search") || void 0;
|
|
1944
2140
|
const user = c.get("user");
|
|
1945
2141
|
let where = void 0;
|
|
1946
2142
|
const whereRaw = c.req.query("where");
|
|
@@ -1961,6 +2157,15 @@ var CollectionController = class {
|
|
|
1961
2157
|
}
|
|
1962
2158
|
}
|
|
1963
2159
|
}
|
|
2160
|
+
const searchWhere = await buildCollectionSearchWhere({
|
|
2161
|
+
collection: this.collection,
|
|
2162
|
+
search,
|
|
2163
|
+
db: readonlyDb,
|
|
2164
|
+
collections: config.collections
|
|
2165
|
+
});
|
|
2166
|
+
if (searchWhere) {
|
|
2167
|
+
where = mergeWhereConstraint(where, searchWhere);
|
|
2168
|
+
}
|
|
1964
2169
|
const beforeReadResult = await runCollectionHooks(
|
|
1965
2170
|
this.collection.hooks?.beforeRead,
|
|
1966
2171
|
{
|
|
@@ -5312,6 +5517,12 @@ function generateOpenApi(config) {
|
|
|
5312
5517
|
schema: { type: "string" },
|
|
5313
5518
|
description: "JSON filter"
|
|
5314
5519
|
},
|
|
5520
|
+
{
|
|
5521
|
+
name: "search",
|
|
5522
|
+
in: "query",
|
|
5523
|
+
schema: { type: "string" },
|
|
5524
|
+
description: "Free-text search across configured searchable fields"
|
|
5525
|
+
},
|
|
5315
5526
|
{
|
|
5316
5527
|
name: "sort",
|
|
5317
5528
|
in: "query",
|
|
@@ -5508,13 +5719,6 @@ function generateOpenApi(config) {
|
|
|
5508
5719
|
}
|
|
5509
5720
|
};
|
|
5510
5721
|
}
|
|
5511
|
-
spec.paths[`${path}/seed`] = {
|
|
5512
|
-
post: {
|
|
5513
|
-
tags: [collectionTag],
|
|
5514
|
-
summary: `Seed initial ${labels.plural}`,
|
|
5515
|
-
responses: { 200: { description: "Seeded documents" } }
|
|
5516
|
-
}
|
|
5517
|
-
};
|
|
5518
5722
|
if (collection.upload) {
|
|
5519
5723
|
spec.paths[`${path}/media`] = {
|
|
5520
5724
|
get: {
|
|
@@ -5816,13 +6020,6 @@ function generateOpenApi(config) {
|
|
|
5816
6020
|
}
|
|
5817
6021
|
}
|
|
5818
6022
|
};
|
|
5819
|
-
spec.paths[`${path}/seed`] = {
|
|
5820
|
-
post: {
|
|
5821
|
-
tags: [globalTag],
|
|
5822
|
-
summary: `Seed ${global.label || slug}`,
|
|
5823
|
-
responses: { 200: { description: "Seeded global" } }
|
|
5824
|
-
}
|
|
5825
|
-
};
|
|
5826
6023
|
}
|
|
5827
6024
|
if (config.storage) {
|
|
5828
6025
|
spec.paths["/api/media/{filename}"] = {
|
|
@@ -6582,7 +6779,6 @@ function registerRoutes(app, config) {
|
|
|
6582
6779
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
6583
6780
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
6584
6781
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
6585
|
-
app.post(`${path}/seed`, (c) => controller.seed(c));
|
|
6586
6782
|
if (collection.auth) {
|
|
6587
6783
|
app.post(
|
|
6588
6784
|
`${path}/:id/change-password`,
|
|
@@ -6608,7 +6804,6 @@ function registerRoutes(app, config) {
|
|
|
6608
6804
|
const controller = new GlobalController(global);
|
|
6609
6805
|
app.get(path, (c) => controller.get(c));
|
|
6610
6806
|
app.patch(path, (c) => controller.update(c));
|
|
6611
|
-
app.post(`${path}/seed`, (c) => controller.seed(c));
|
|
6612
6807
|
}
|
|
6613
6808
|
if (!process.env.DYRECTED_JWT_SECRET) {
|
|
6614
6809
|
getConfigLogger(config, "router").warn({
|
|
@@ -6757,7 +6952,6 @@ function registerRoutes(app, config) {
|
|
|
6757
6952
|
return controller.deleteMany(c);
|
|
6758
6953
|
if (method === "DELETE") return controller.delete(c);
|
|
6759
6954
|
if (method === "POST" && id === "media") return controller.create(c);
|
|
6760
|
-
if (method === "POST" && id === "seed") return controller.seed(c);
|
|
6761
6955
|
} else {
|
|
6762
6956
|
if (method === "GET") return controller.find(c);
|
|
6763
6957
|
if (method === "POST") return controller.create(c);
|
|
@@ -6783,9 +6977,7 @@ function registerRoutes(app, config) {
|
|
|
6783
6977
|
if (global) {
|
|
6784
6978
|
const controller = new GlobalController(global);
|
|
6785
6979
|
const method = c.req.method;
|
|
6786
|
-
if (id) {
|
|
6787
|
-
if (method === "POST" && id === "seed") return controller.seed(c);
|
|
6788
|
-
} else {
|
|
6980
|
+
if (!id) {
|
|
6789
6981
|
if (method === "GET") return controller.get(c);
|
|
6790
6982
|
if (method === "PATCH") return controller.update(c);
|
|
6791
6983
|
}
|
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 { Hono, Context } from 'hono';
|
|
4
4
|
import { Logger } from 'pino';
|
|
5
|
-
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-
|
|
5
|
+
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-DbK2Tv6i.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';
|
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 { Hono, Context } from 'hono';
|
|
4
4
|
import { Logger } from 'pino';
|
|
5
|
-
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-
|
|
5
|
+
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-DbK2Tv6i.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';
|
package/dist/server.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
touchAuthSession,
|
|
37
37
|
transitionWorkflow,
|
|
38
38
|
verifyCollectionToken
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-K3ASMUKV.js";
|
|
40
40
|
|
|
41
41
|
// src/app.ts
|
|
42
42
|
import { Hono } from "hono";
|
|
@@ -110,7 +110,7 @@ var PopulationService = class {
|
|
|
110
110
|
}
|
|
111
111
|
const populatedDoc = { ...data };
|
|
112
112
|
for (const field of fields) {
|
|
113
|
-
if (field.type === "join" && field.collection && field.on && currentDepth
|
|
113
|
+
if (field.type === "join" && field.collection && field.on && currentDepth < maxDepth) {
|
|
114
114
|
const targetCollection = this.collections.find(
|
|
115
115
|
(c) => c.slug === field.collection
|
|
116
116
|
);
|
|
@@ -129,7 +129,7 @@ var PopulationService = class {
|
|
|
129
129
|
(doc) => DefaultsService.apply(targetCollection.fields, doc)
|
|
130
130
|
),
|
|
131
131
|
fields: targetCollection.fields,
|
|
132
|
-
currentDepth: 1,
|
|
132
|
+
currentDepth: currentDepth + 1,
|
|
133
133
|
maxDepth
|
|
134
134
|
});
|
|
135
135
|
populatedDoc[field.name] = {
|
|
@@ -487,6 +487,191 @@ async function resolveAccess(config, access, args) {
|
|
|
487
487
|
return false;
|
|
488
488
|
}
|
|
489
489
|
|
|
490
|
+
// src/utils/collection-search.ts
|
|
491
|
+
var DEFAULT_SEARCHABLE_NAMES = /* @__PURE__ */ new Set([
|
|
492
|
+
"title",
|
|
493
|
+
"name",
|
|
494
|
+
"label",
|
|
495
|
+
"slug",
|
|
496
|
+
"email",
|
|
497
|
+
"caption",
|
|
498
|
+
"description"
|
|
499
|
+
]);
|
|
500
|
+
var MAX_RELATION_MATCHES = 50;
|
|
501
|
+
var MAX_RELATION_TITLE_DEPTH = 8;
|
|
502
|
+
var TITLE_FALLBACK_NAMES = ["title", "name", "label", "heading", "email", "subject"];
|
|
503
|
+
function isScalarSearchField(field) {
|
|
504
|
+
return field.type === "text" || field.type === "textarea" || field.type === "richText" || field.type === "email";
|
|
505
|
+
}
|
|
506
|
+
function findFieldByName(collection, fieldName) {
|
|
507
|
+
if (!fieldName) return void 0;
|
|
508
|
+
return collection.fields.find((field) => field.name === fieldName);
|
|
509
|
+
}
|
|
510
|
+
function resolveCollectionTitleField(collection) {
|
|
511
|
+
const configured = findFieldByName(collection, collection.admin?.useAsTitle);
|
|
512
|
+
if (configured) return configured;
|
|
513
|
+
for (const name of TITLE_FALLBACK_NAMES) {
|
|
514
|
+
const field = findFieldByName(collection, name);
|
|
515
|
+
if (field) return field;
|
|
516
|
+
}
|
|
517
|
+
return collection.fields.find((field) => !!field.name && field.type !== "join" && field.type !== "row");
|
|
518
|
+
}
|
|
519
|
+
function getCollectionSearchableFields(collection) {
|
|
520
|
+
const fieldByName = new Map(
|
|
521
|
+
collection.fields.filter((field) => !!field.name).map((field) => [field.name, field])
|
|
522
|
+
);
|
|
523
|
+
const configured = collection.admin?.searchableFields?.map((fieldName) => fieldByName.get(fieldName)).filter((field) => !!field);
|
|
524
|
+
if (configured && configured.length > 0) {
|
|
525
|
+
return configured;
|
|
526
|
+
}
|
|
527
|
+
const results = [];
|
|
528
|
+
const pushUnique = (field) => {
|
|
529
|
+
if (!field?.name) return;
|
|
530
|
+
if (results.some((item) => item.name === field.name)) return;
|
|
531
|
+
results.push(field);
|
|
532
|
+
};
|
|
533
|
+
pushUnique(findFieldByName(collection, collection.admin?.useAsTitle));
|
|
534
|
+
for (const field of collection.fields) {
|
|
535
|
+
if (!field.name) continue;
|
|
536
|
+
if (isScalarSearchField(field) && DEFAULT_SEARCHABLE_NAMES.has(field.name)) {
|
|
537
|
+
pushUnique(field);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return results;
|
|
541
|
+
}
|
|
542
|
+
function buildScalarSearchClause(fieldName, search) {
|
|
543
|
+
return { [fieldName]: { contains: search } };
|
|
544
|
+
}
|
|
545
|
+
function getFallbackTitleFields(collection) {
|
|
546
|
+
const fields = [];
|
|
547
|
+
for (const name of TITLE_FALLBACK_NAMES) {
|
|
548
|
+
const field = findFieldByName(collection, name);
|
|
549
|
+
if (!field?.name || !isScalarSearchField(field)) continue;
|
|
550
|
+
if (fields.some((item) => item.name === field.name)) continue;
|
|
551
|
+
fields.push(field);
|
|
552
|
+
}
|
|
553
|
+
return fields;
|
|
554
|
+
}
|
|
555
|
+
async function findMatchingDocumentIdsByWhere(args) {
|
|
556
|
+
const { collection, where, db } = args;
|
|
557
|
+
if (!where) return [];
|
|
558
|
+
const matches = await db.find({
|
|
559
|
+
collection: collection.slug,
|
|
560
|
+
where,
|
|
561
|
+
limit: MAX_RELATION_MATCHES
|
|
562
|
+
});
|
|
563
|
+
return matches.docs.map((doc) => doc?.id).filter((id) => typeof id === "string");
|
|
564
|
+
}
|
|
565
|
+
function buildMultiRelationshipClause(fieldName, ids) {
|
|
566
|
+
const matches = ids.map((id) => ({
|
|
567
|
+
OR: [
|
|
568
|
+
{ [fieldName]: { contains: `"${id}"` } },
|
|
569
|
+
{ [fieldName]: { contains: id } }
|
|
570
|
+
]
|
|
571
|
+
}));
|
|
572
|
+
if (matches.length === 0) return void 0;
|
|
573
|
+
return matches.length === 1 ? matches[0] : { OR: matches };
|
|
574
|
+
}
|
|
575
|
+
function buildRelationshipMatchClause(field, ids) {
|
|
576
|
+
if (!field.name || ids.length === 0) return void 0;
|
|
577
|
+
if (field.hasMany) {
|
|
578
|
+
return buildMultiRelationshipClause(field.name, ids);
|
|
579
|
+
}
|
|
580
|
+
return { [field.name]: { in: ids } };
|
|
581
|
+
}
|
|
582
|
+
async function findMatchingDocumentIdsByTitle(args) {
|
|
583
|
+
const { collection, search, db, collections, depth = 0 } = args;
|
|
584
|
+
if (depth >= MAX_RELATION_TITLE_DEPTH) {
|
|
585
|
+
return [];
|
|
586
|
+
}
|
|
587
|
+
const ids = /* @__PURE__ */ new Set();
|
|
588
|
+
const titleField = resolveCollectionTitleField(collection);
|
|
589
|
+
if (titleField?.name && isScalarSearchField(titleField)) {
|
|
590
|
+
for (const id of await findMatchingDocumentIdsByWhere({
|
|
591
|
+
collection,
|
|
592
|
+
where: buildScalarSearchClause(titleField.name, search),
|
|
593
|
+
db
|
|
594
|
+
})) {
|
|
595
|
+
ids.add(id);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (titleField?.type === "relationship" && titleField.relationTo) {
|
|
599
|
+
for (const field of getFallbackTitleFields(collection)) {
|
|
600
|
+
for (const id of await findMatchingDocumentIdsByWhere({
|
|
601
|
+
collection,
|
|
602
|
+
where: buildScalarSearchClause(field.name, search),
|
|
603
|
+
db
|
|
604
|
+
})) {
|
|
605
|
+
ids.add(id);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const relatedCollection = collections.find(
|
|
609
|
+
(item) => item.slug === titleField.relationTo
|
|
610
|
+
);
|
|
611
|
+
if (relatedCollection) {
|
|
612
|
+
const relatedIds = await findMatchingDocumentIdsByTitle({
|
|
613
|
+
collection: relatedCollection,
|
|
614
|
+
search,
|
|
615
|
+
db,
|
|
616
|
+
collections,
|
|
617
|
+
depth: depth + 1
|
|
618
|
+
});
|
|
619
|
+
const relationClause = buildRelationshipMatchClause(titleField, relatedIds);
|
|
620
|
+
for (const id of await findMatchingDocumentIdsByWhere({
|
|
621
|
+
collection,
|
|
622
|
+
where: relationClause,
|
|
623
|
+
db
|
|
624
|
+
})) {
|
|
625
|
+
ids.add(id);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return Array.from(ids).slice(0, MAX_RELATION_MATCHES);
|
|
630
|
+
}
|
|
631
|
+
async function buildRelationshipSearchClause(args) {
|
|
632
|
+
const { field, search, db, collections } = args;
|
|
633
|
+
if (!field.name || field.type !== "relationship" || !field.relationTo) {
|
|
634
|
+
return void 0;
|
|
635
|
+
}
|
|
636
|
+
const relatedCollection = collections.find(
|
|
637
|
+
(collection) => collection.slug === field.relationTo
|
|
638
|
+
);
|
|
639
|
+
if (!relatedCollection) return void 0;
|
|
640
|
+
const ids = await findMatchingDocumentIdsByTitle({
|
|
641
|
+
collection: relatedCollection,
|
|
642
|
+
search,
|
|
643
|
+
db,
|
|
644
|
+
collections
|
|
645
|
+
});
|
|
646
|
+
return buildRelationshipMatchClause(field, ids);
|
|
647
|
+
}
|
|
648
|
+
async function buildCollectionSearchWhere(args) {
|
|
649
|
+
const { collection, search, db, collections } = args;
|
|
650
|
+
const trimmedSearch = search?.trim();
|
|
651
|
+
if (!trimmedSearch) return void 0;
|
|
652
|
+
const searchableFields = getCollectionSearchableFields(collection);
|
|
653
|
+
if (searchableFields.length === 0) return void 0;
|
|
654
|
+
const clauses = [];
|
|
655
|
+
for (const field of searchableFields) {
|
|
656
|
+
if (!field.name) continue;
|
|
657
|
+
if (isScalarSearchField(field)) {
|
|
658
|
+
clauses.push(buildScalarSearchClause(field.name, trimmedSearch));
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (field.type === "relationship") {
|
|
662
|
+
const clause = await buildRelationshipSearchClause({
|
|
663
|
+
field,
|
|
664
|
+
search: trimmedSearch,
|
|
665
|
+
db,
|
|
666
|
+
collections
|
|
667
|
+
});
|
|
668
|
+
if (clause) clauses.push(clause);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (clauses.length === 0) return void 0;
|
|
672
|
+
return clauses.length === 1 ? clauses[0] : { OR: clauses };
|
|
673
|
+
}
|
|
674
|
+
|
|
490
675
|
// src/utils/access-control.ts
|
|
491
676
|
function toHookRequestContext(req) {
|
|
492
677
|
return {
|
|
@@ -711,6 +896,7 @@ var CollectionController = class {
|
|
|
711
896
|
const limit2 = Number(c.req.query("limit")) || 10;
|
|
712
897
|
const page2 = Number(c.req.query("page")) || 1;
|
|
713
898
|
const sort2 = c.req.query("sort") || void 0;
|
|
899
|
+
const search2 = c.req.query("search") || void 0;
|
|
714
900
|
let where2 = void 0;
|
|
715
901
|
const whereRaw2 = c.req.query("where");
|
|
716
902
|
if (whereRaw2) {
|
|
@@ -719,6 +905,15 @@ var CollectionController = class {
|
|
|
719
905
|
} catch {
|
|
720
906
|
}
|
|
721
907
|
}
|
|
908
|
+
const searchWhere2 = await buildCollectionSearchWhere({
|
|
909
|
+
collection: this.collection,
|
|
910
|
+
search: search2,
|
|
911
|
+
db: createReadonlyDb(db),
|
|
912
|
+
collections: config.collections
|
|
913
|
+
});
|
|
914
|
+
if (searchWhere2) {
|
|
915
|
+
where2 = mergeWhereConstraint(where2, searchWhere2);
|
|
916
|
+
}
|
|
722
917
|
const paginatedResult = await provider.members.list({
|
|
723
918
|
limit: limit2,
|
|
724
919
|
page: page2,
|
|
@@ -753,6 +948,7 @@ var CollectionController = class {
|
|
|
753
948
|
const page = Number(c.req.query("page")) || 1;
|
|
754
949
|
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
755
950
|
const sort = c.req.query("sort") || void 0;
|
|
951
|
+
const search = c.req.query("search") || void 0;
|
|
756
952
|
const user = c.get("user");
|
|
757
953
|
let where = void 0;
|
|
758
954
|
const whereRaw = c.req.query("where");
|
|
@@ -773,6 +969,15 @@ var CollectionController = class {
|
|
|
773
969
|
}
|
|
774
970
|
}
|
|
775
971
|
}
|
|
972
|
+
const searchWhere = await buildCollectionSearchWhere({
|
|
973
|
+
collection: this.collection,
|
|
974
|
+
search,
|
|
975
|
+
db: readonlyDb,
|
|
976
|
+
collections: config.collections
|
|
977
|
+
});
|
|
978
|
+
if (searchWhere) {
|
|
979
|
+
where = mergeWhereConstraint(where, searchWhere);
|
|
980
|
+
}
|
|
776
981
|
const beforeReadResult = await runCollectionHooks(
|
|
777
982
|
this.collection.hooks?.beforeRead,
|
|
778
983
|
{
|
|
@@ -4321,7 +4526,6 @@ function registerRoutes(app, config) {
|
|
|
4321
4526
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
4322
4527
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
4323
4528
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
4324
|
-
app.post(`${path}/seed`, (c) => controller.seed(c));
|
|
4325
4529
|
if (collection.auth) {
|
|
4326
4530
|
app.post(
|
|
4327
4531
|
`${path}/:id/change-password`,
|
|
@@ -4347,7 +4551,6 @@ function registerRoutes(app, config) {
|
|
|
4347
4551
|
const controller = new GlobalController(global);
|
|
4348
4552
|
app.get(path, (c) => controller.get(c));
|
|
4349
4553
|
app.patch(path, (c) => controller.update(c));
|
|
4350
|
-
app.post(`${path}/seed`, (c) => controller.seed(c));
|
|
4351
4554
|
}
|
|
4352
4555
|
if (!process.env.DYRECTED_JWT_SECRET) {
|
|
4353
4556
|
getConfigLogger(config, "router").warn({
|
|
@@ -4496,7 +4699,6 @@ function registerRoutes(app, config) {
|
|
|
4496
4699
|
return controller.deleteMany(c);
|
|
4497
4700
|
if (method === "DELETE") return controller.delete(c);
|
|
4498
4701
|
if (method === "POST" && id === "media") return controller.create(c);
|
|
4499
|
-
if (method === "POST" && id === "seed") return controller.seed(c);
|
|
4500
4702
|
} else {
|
|
4501
4703
|
if (method === "GET") return controller.find(c);
|
|
4502
4704
|
if (method === "POST") return controller.create(c);
|
|
@@ -4522,9 +4724,7 @@ function registerRoutes(app, config) {
|
|
|
4522
4724
|
if (global) {
|
|
4523
4725
|
const controller = new GlobalController(global);
|
|
4524
4726
|
const method = c.req.method;
|
|
4525
|
-
if (id) {
|
|
4526
|
-
if (method === "POST" && id === "seed") return controller.seed(c);
|
|
4527
|
-
} else {
|
|
4727
|
+
if (!id) {
|
|
4528
4728
|
if (method === "GET") return controller.get(c);
|
|
4529
4729
|
if (method === "PATCH") return controller.update(c);
|
|
4530
4730
|
}
|