@baasix/baasix 0.1.61 → 0.1.63
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/404/index.html +1 -1
- package/dist/app/404.html +1 -1
- package/dist/app/_next/static/chunks/2667-7993936698bf75a9.js +1 -0
- package/dist/app/_next/static/chunks/3233-ae028c73c510ebea.js +1 -0
- package/dist/app/_next/static/chunks/4033-ea89dd457b87cead.js +1 -0
- package/dist/app/_next/static/chunks/799-d4372ad380b0dec6.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/all-activity/{page-a7e9f0b39624b489.js → page-8e1f084e807ff5ee.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/email-log/{page-40f2b9ecf73e74b5.js → page-4e08df2865fe9d0f.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/notifications/{page-21fcdd2f0b3549db.js → page-3a5070a5088835ae.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/sessions/{page-82662735f1ec476a.js → page-edfa379cab0d3ccb.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/workflow-executions/{page-4a781c95e5fb25f0.js → page-d007890f17b60862.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/workflow-logs/{page-70bf8d31074dcba0.js → page-976e9829706ac8f2.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/dashboard/page-6bff6e8911fc5036.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/data-browser/{page-d5715b9743d8dba4.js → page-fbb7a4d4f2a4353d.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/file-manager/{page-c008de2bcf6811b9.js → page-cea6a2c1670d4ef5.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/layout-b7f18737e4c308a4.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/migrations/page-505c3389890685b9.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/permissions/{page-2d3043500adcdb12.js → page-ed80f4bd5d81df95.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/project/{page-6a58f15efb013e17.js → page-310b8ce72d0516ea.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/roles/{page-00cf3e4483986407.js → page-fd62dc19ac88dd64.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/schema/page-3d91124704c2e853.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/schema-diff/page-cae8772a7ae93b9a.js +1 -0
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/tasks/{page-fa3c401992a2fb8f.js → page-1479604bbfc53a83.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/templates/edit/{page-4ce715d527c717a8.js → page-a611195661ce22bd.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/templates/{page-aafbfd4f12875738.js → page-6787b62a4960304f.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/tenants/{page-f34d97ce6f68183b.js → page-856d4af5fbd76c8e.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/users/invites/{page-20dd05a8745b6283.js → page-ed5493d55f6c1db8.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/users/list/{page-479a164304f4de3f.js → page-02e6ef533bdc3bbc.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/users/{page-909d7be1b8d9800c.js → page-faf321b61a6fdeef.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/users/preferences/{page-e3fbcc425e1710e5.js → page-2116015e962a2ecc.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/users/user-roles/{page-3f654c69d79269f7.js → page-7d1bcbbfc8f0694b.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/workflows/edit/{page-753cf274fd329860.js → page-45c465caaa5679bd.js} +1 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/workflows/{page-33c1c69678122839.js → page-7f9cb24aaa798f42.js} +1 -1
- package/dist/app/_next/static/chunks/{main-324e91f5a430cddf.js → main-8ffa7219a08e42cf.js} +1 -1
- package/dist/app/_next/static/chunks/{webpack-b29732cfe967a5f5.js → webpack-7a100cbf9402e7ea.js} +1 -1
- package/dist/app/_next/static/css/45276c37be155f45.css +3 -0
- package/dist/app/activity-log/all-activity/index.html +1 -1
- package/dist/app/activity-log/all-activity/index.txt +3 -3
- package/dist/app/activity-log/email-log/index.html +1 -1
- package/dist/app/activity-log/email-log/index.txt +3 -3
- package/dist/app/activity-log/index.html +1 -1
- package/dist/app/activity-log/index.txt +2 -2
- package/dist/app/activity-log/notifications/index.html +1 -1
- package/dist/app/activity-log/notifications/index.txt +3 -3
- package/dist/app/activity-log/sessions/index.html +1 -1
- package/dist/app/activity-log/sessions/index.txt +3 -3
- package/dist/app/activity-log/workflow-executions/index.html +1 -1
- package/dist/app/activity-log/workflow-executions/index.txt +3 -3
- package/dist/app/activity-log/workflow-logs/index.html +1 -1
- package/dist/app/activity-log/workflow-logs/index.txt +3 -3
- package/dist/app/change-password/index.html +1 -1
- package/dist/app/change-password/index.txt +2 -2
- package/dist/app/dashboard/index.html +1 -1
- package/dist/app/dashboard/index.txt +3 -3
- package/dist/app/data-browser/index.html +1 -1
- package/dist/app/data-browser/index.txt +3 -3
- package/dist/app/file-manager/index.html +1 -1
- package/dist/app/file-manager/index.txt +3 -3
- package/dist/app/forgot-password/index.html +1 -1
- package/dist/app/forgot-password/index.txt +1 -1
- package/dist/app/index.html +1 -1
- package/dist/app/index.txt +1 -1
- package/dist/app/login/index.html +1 -1
- package/dist/app/login/index.txt +1 -1
- package/dist/app/register/index.html +1 -1
- package/dist/app/register/index.txt +1 -1
- package/dist/app/settings/migrations/index.html +1 -1
- package/dist/app/settings/migrations/index.txt +3 -3
- package/dist/app/settings/permissions/index.html +1 -1
- package/dist/app/settings/permissions/index.txt +3 -3
- package/dist/app/settings/project/index.html +1 -1
- package/dist/app/settings/project/index.txt +3 -3
- package/dist/app/settings/roles/index.html +1 -1
- package/dist/app/settings/roles/index.txt +3 -3
- package/dist/app/settings/schema/index.html +1 -1
- package/dist/app/settings/schema/index.txt +3 -3
- package/dist/app/settings/schema-diff/index.html +1 -0
- package/dist/app/settings/schema-diff/index.txt +14 -0
- package/dist/app/settings/tasks/index.html +1 -1
- package/dist/app/settings/tasks/index.txt +3 -3
- package/dist/app/settings/templates/edit/index.html +1 -1
- package/dist/app/settings/templates/edit/index.txt +3 -3
- package/dist/app/settings/templates/index.html +1 -1
- package/dist/app/settings/templates/index.txt +3 -3
- package/dist/app/settings/tenants/index.html +1 -1
- package/dist/app/settings/tenants/index.txt +3 -3
- package/dist/app/users/index.html +1 -1
- package/dist/app/users/index.txt +3 -3
- package/dist/app/users/invites/index.html +1 -1
- package/dist/app/users/invites/index.txt +3 -3
- package/dist/app/users/list/index.html +1 -1
- package/dist/app/users/list/index.txt +3 -3
- package/dist/app/users/preferences/index.html +1 -1
- package/dist/app/users/preferences/index.txt +3 -3
- package/dist/app/users/user-roles/index.html +1 -1
- package/dist/app/users/user-roles/index.txt +3 -3
- package/dist/app/workflows/detail/index.html +1 -1
- package/dist/app/workflows/detail/index.txt +2 -2
- package/dist/app/workflows/edit/index.html +1 -1
- package/dist/app/workflows/edit/index.txt +3 -3
- package/dist/app/workflows/execution/index.html +1 -1
- package/dist/app/workflows/execution/index.txt +2 -2
- package/dist/app/workflows/index.html +1 -1
- package/dist/app/workflows/index.txt +3 -3
- package/dist/routes/mcp.route.d.ts.map +1 -1
- package/dist/routes/mcp.route.js +283 -2
- package/dist/routes/mcp.route.js.map +1 -1
- package/dist/routes/schema-diff.route.d.ts +7 -0
- package/dist/routes/schema-diff.route.d.ts.map +1 -0
- package/dist/routes/schema-diff.route.js +320 -0
- package/dist/routes/schema-diff.route.js.map +1 -0
- package/dist/services/ItemsService.d.ts +5 -4
- package/dist/services/ItemsService.d.ts.map +1 -1
- package/dist/services/ItemsService.js +30 -26
- package/dist/services/ItemsService.js.map +1 -1
- package/package.json +1 -1
- package/dist/app/_next/static/chunks/2033-8355304f13887db5.js +0 -1
- package/dist/app/_next/static/chunks/4952-1b97320cf61f3f21.js +0 -1
- package/dist/app/_next/static/chunks/6547-1aaab011e6089042.js +0 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/dashboard/page-1ceeac9e72997a8a.js +0 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/layout-2a4b221af5cbbade.js +0 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/migrations/page-6681c14dd478cc9a.js +0 -1
- package/dist/app/_next/static/chunks/app/(authenticated)/settings/schema/page-e7f836d3a7f7c5d0.js +0 -1
- package/dist/app/_next/static/css/17a2e01c4184b106.css +0 -3
- /package/dist/app/_next/static/{ORfvBD2VaKIB_8_7O9AgQ → O6PA41CeUjDJQ4sma7vMH}/_buildManifest.js +0 -0
- /package/dist/app/_next/static/{ORfvBD2VaKIB_8_7O9AgQ → O6PA41CeUjDJQ4sma7vMH}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { getSqlClient, getDatabase } from "../utils/db.js";
|
|
2
|
+
import { schemaManager } from "../utils/schemaManager.js";
|
|
3
|
+
import { adminOnly } from "../utils/auth.js";
|
|
4
|
+
import { APIError } from "../utils/errorHandler.js";
|
|
5
|
+
/**
|
|
6
|
+
* Determines which schema fields should produce actual database columns.
|
|
7
|
+
* Relation-only fields (no explicit type) do NOT produce columns,
|
|
8
|
+
* but BelongsTo relations produce a foreignKey column.
|
|
9
|
+
*/
|
|
10
|
+
function getExpectedColumns(schema) {
|
|
11
|
+
const columns = new Map();
|
|
12
|
+
for (const [fieldName, fieldSchema] of Object.entries(schema.fields)) {
|
|
13
|
+
const fs = fieldSchema;
|
|
14
|
+
// BelongsTo relations create a foreign key column
|
|
15
|
+
if (fs.relType === "BelongsTo") {
|
|
16
|
+
const foreignKey = fs.foreignKey || `${fieldName}_Id`;
|
|
17
|
+
if (foreignKey === fieldName && fs.type) {
|
|
18
|
+
// Field itself is the FK column with explicit type
|
|
19
|
+
columns.set(fieldName, { type: fs.type, fromField: fieldName });
|
|
20
|
+
}
|
|
21
|
+
else if (foreignKey !== fieldName) {
|
|
22
|
+
// Separate FK column
|
|
23
|
+
columns.set(foreignKey, { type: fs.type || "UUID", fromField: fieldName });
|
|
24
|
+
}
|
|
25
|
+
// If foreignKey === fieldName but no explicit type, handled by FK logic
|
|
26
|
+
if (foreignKey === fieldName && !fs.type) {
|
|
27
|
+
columns.set(foreignKey, { type: "UUID", fromField: fieldName });
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
// Skip relation-only fields (no explicit type)
|
|
32
|
+
if (fs.relType && !fs.type)
|
|
33
|
+
continue;
|
|
34
|
+
columns.set(fieldName, { type: fs.type || "String", fromField: fieldName });
|
|
35
|
+
}
|
|
36
|
+
// Implicit columns from schema flags
|
|
37
|
+
if (schema.timestamps !== false) {
|
|
38
|
+
if (!columns.has("createdAt"))
|
|
39
|
+
columns.set("createdAt", { type: "DateTime", fromField: "[timestamps]" });
|
|
40
|
+
if (!columns.has("updatedAt"))
|
|
41
|
+
columns.set("updatedAt", { type: "DateTime", fromField: "[timestamps]" });
|
|
42
|
+
}
|
|
43
|
+
if (schema.paranoid) {
|
|
44
|
+
if (!columns.has("deletedAt"))
|
|
45
|
+
columns.set("deletedAt", { type: "DateTime", fromField: "[paranoid]" });
|
|
46
|
+
}
|
|
47
|
+
if (schema.sortEnabled) {
|
|
48
|
+
if (!columns.has("sort"))
|
|
49
|
+
columns.set("sort", { type: "Integer", fromField: "[sortEnabled]" });
|
|
50
|
+
}
|
|
51
|
+
if (schema.usertrack) {
|
|
52
|
+
if (!columns.has("userCreated"))
|
|
53
|
+
columns.set("userCreated", { type: "UUID", fromField: "[usertrack]" });
|
|
54
|
+
if (!columns.has("userUpdated"))
|
|
55
|
+
columns.set("userUpdated", { type: "UUID", fromField: "[usertrack]" });
|
|
56
|
+
}
|
|
57
|
+
return columns;
|
|
58
|
+
}
|
|
59
|
+
const registerEndpoint = (app, context) => {
|
|
60
|
+
/**
|
|
61
|
+
* GET /utils/schema-diff
|
|
62
|
+
* Compare schema definitions with actual database tables/columns.
|
|
63
|
+
* Admin-only endpoint.
|
|
64
|
+
*/
|
|
65
|
+
app.get("/utils/schema-diff", adminOnly, async (req, res, next) => {
|
|
66
|
+
try {
|
|
67
|
+
const sql = getSqlClient();
|
|
68
|
+
// 1. Get all tables in the public schema from the actual database
|
|
69
|
+
// Tables to exclude from comparison (e.g. PostGIS internal tables)
|
|
70
|
+
const excludedTables = ['spatial_ref_sys'];
|
|
71
|
+
const dbTables = await sql `
|
|
72
|
+
SELECT table_name
|
|
73
|
+
FROM information_schema.tables
|
|
74
|
+
WHERE table_schema = 'public'
|
|
75
|
+
AND table_type = 'BASE TABLE'
|
|
76
|
+
AND table_name != ALL(${excludedTables})
|
|
77
|
+
ORDER BY table_name
|
|
78
|
+
`;
|
|
79
|
+
const dbTableNames = new Set(dbTables.map((t) => t.table_name));
|
|
80
|
+
// 2. Get all columns per table from the database
|
|
81
|
+
const dbColumns = await sql `
|
|
82
|
+
SELECT table_name, column_name, data_type, is_nullable, column_default,
|
|
83
|
+
character_maximum_length, numeric_precision, numeric_scale
|
|
84
|
+
FROM information_schema.columns
|
|
85
|
+
WHERE table_schema = 'public'
|
|
86
|
+
AND table_name != ALL(${excludedTables})
|
|
87
|
+
ORDER BY table_name, ordinal_position
|
|
88
|
+
`;
|
|
89
|
+
const dbColumnsByTable = new Map();
|
|
90
|
+
for (const col of dbColumns) {
|
|
91
|
+
if (!dbColumnsByTable.has(col.table_name)) {
|
|
92
|
+
dbColumnsByTable.set(col.table_name, []);
|
|
93
|
+
}
|
|
94
|
+
dbColumnsByTable.get(col.table_name).push(col);
|
|
95
|
+
}
|
|
96
|
+
// 3. Get all schema definitions from baasix
|
|
97
|
+
const allSchemaDefinitions = new Map();
|
|
98
|
+
// Access internal schemaDefinitions via the public API
|
|
99
|
+
const schemasMap = schemaManager.getAllSchemas();
|
|
100
|
+
for (const [collectionName] of schemasMap) {
|
|
101
|
+
const schemaDef = await schemaManager.getSchemaDefinition(collectionName);
|
|
102
|
+
if (schemaDef) {
|
|
103
|
+
allSchemaDefinitions.set(collectionName, schemaDef);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const schemaCollectionNames = new Set(allSchemaDefinitions.keys());
|
|
107
|
+
// 4. Compute diffs
|
|
108
|
+
// Tables in schema but not in database
|
|
109
|
+
const tablesOnlyInSchema = [];
|
|
110
|
+
for (const name of schemaCollectionNames) {
|
|
111
|
+
if (!dbTableNames.has(name)) {
|
|
112
|
+
tablesOnlyInSchema.push(name);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Tables in database but not in schema
|
|
116
|
+
const tablesOnlyInDb = [];
|
|
117
|
+
for (const name of dbTableNames) {
|
|
118
|
+
if (!schemaCollectionNames.has(name)) {
|
|
119
|
+
tablesOnlyInDb.push(name);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Per-table field diffs (only for tables that exist in both)
|
|
123
|
+
const fieldDiffs = [];
|
|
124
|
+
for (const [collectionName, schemaDef] of allSchemaDefinitions) {
|
|
125
|
+
if (!dbTableNames.has(collectionName))
|
|
126
|
+
continue; // table doesn't exist in DB
|
|
127
|
+
const expectedColumns = getExpectedColumns(schemaDef);
|
|
128
|
+
const actualColumns = dbColumnsByTable.get(collectionName) || [];
|
|
129
|
+
const actualColumnNames = new Set(actualColumns.map((c) => c.column_name));
|
|
130
|
+
const fieldsOnlyInSchema = [];
|
|
131
|
+
const fieldsOnlyInDb = [];
|
|
132
|
+
// Fields in schema but not in database
|
|
133
|
+
for (const [colName, meta] of expectedColumns) {
|
|
134
|
+
if (!actualColumnNames.has(colName)) {
|
|
135
|
+
fieldsOnlyInSchema.push({ column: colName, type: meta.type, fromField: meta.fromField });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Fields in database but not in schema
|
|
139
|
+
for (const col of actualColumns) {
|
|
140
|
+
if (!expectedColumns.has(col.column_name)) {
|
|
141
|
+
fieldsOnlyInDb.push({
|
|
142
|
+
column: col.column_name,
|
|
143
|
+
dataType: col.data_type,
|
|
144
|
+
isNullable: col.is_nullable,
|
|
145
|
+
columnDefault: col.column_default,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (fieldsOnlyInSchema.length > 0 || fieldsOnlyInDb.length > 0) {
|
|
150
|
+
fieldDiffs.push({
|
|
151
|
+
collection: collectionName,
|
|
152
|
+
fieldsOnlyInSchema,
|
|
153
|
+
fieldsOnlyInDb,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// 5. Summary
|
|
158
|
+
const summary = {
|
|
159
|
+
totalSchemaCollections: schemaCollectionNames.size,
|
|
160
|
+
totalDbTables: dbTableNames.size,
|
|
161
|
+
tablesOnlyInSchemaCount: tablesOnlyInSchema.length,
|
|
162
|
+
tablesOnlyInDbCount: tablesOnlyInDb.length,
|
|
163
|
+
collectionsWithFieldDiffs: fieldDiffs.length,
|
|
164
|
+
};
|
|
165
|
+
return res.status(200).json({
|
|
166
|
+
data: {
|
|
167
|
+
summary,
|
|
168
|
+
tablesOnlyInSchema,
|
|
169
|
+
tablesOnlyInDb,
|
|
170
|
+
fieldDiffs,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
next(error);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
/**
|
|
179
|
+
* DELETE /utils/schema-diff/table/schema/:collection
|
|
180
|
+
* Remove a collection from schema definitions (keeps database table).
|
|
181
|
+
*/
|
|
182
|
+
app.delete("/utils/schema-diff/table/schema/:collection", adminOnly, async (req, res, next) => {
|
|
183
|
+
try {
|
|
184
|
+
const { collection } = req.params;
|
|
185
|
+
// Verify collection exists in schema
|
|
186
|
+
const schemaDef = await schemaManager.getSchemaDefinition(collection);
|
|
187
|
+
if (!schemaDef) {
|
|
188
|
+
throw new APIError(`Collection '${collection}' not found in schema definitions`, 404);
|
|
189
|
+
}
|
|
190
|
+
// Prevent deleting system schemas
|
|
191
|
+
if (collection.startsWith("baasix_")) {
|
|
192
|
+
throw new APIError(`Cannot remove system schema '${collection}'`, 400);
|
|
193
|
+
}
|
|
194
|
+
await schemaManager.removeSchemaDefinition(collection);
|
|
195
|
+
return res.status(200).json({
|
|
196
|
+
data: { message: `Schema definition '${collection}' removed successfully` },
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
next(error);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
/**
|
|
204
|
+
* DELETE /utils/schema-diff/table/db/:table
|
|
205
|
+
* Drop a table from the database (keeps schema definition).
|
|
206
|
+
*/
|
|
207
|
+
app.delete("/utils/schema-diff/table/db/:table", adminOnly, async (req, res, next) => {
|
|
208
|
+
try {
|
|
209
|
+
const { table } = req.params;
|
|
210
|
+
const sql = getSqlClient();
|
|
211
|
+
// Validate table name: only allow alphanumeric and underscores
|
|
212
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
|
|
213
|
+
throw new APIError("Invalid table name", 400);
|
|
214
|
+
}
|
|
215
|
+
// Verify table exists
|
|
216
|
+
const exists = await sql `
|
|
217
|
+
SELECT EXISTS (
|
|
218
|
+
SELECT FROM information_schema.tables
|
|
219
|
+
WHERE table_schema = 'public' AND table_name = ${table}
|
|
220
|
+
)
|
|
221
|
+
`;
|
|
222
|
+
if (!exists[0].exists) {
|
|
223
|
+
throw new APIError(`Table '${table}' not found in database`, 404);
|
|
224
|
+
}
|
|
225
|
+
// Prevent dropping system tables
|
|
226
|
+
if (table.startsWith("baasix_")) {
|
|
227
|
+
throw new APIError(`Cannot drop system table '${table}'`, 400);
|
|
228
|
+
}
|
|
229
|
+
await sql.unsafe(`DROP TABLE IF EXISTS "${table}" CASCADE`);
|
|
230
|
+
return res.status(200).json({
|
|
231
|
+
data: { message: `Table '${table}' dropped from database successfully` },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
next(error);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
/**
|
|
239
|
+
* DELETE /utils/schema-diff/field/schema/:collection/:field
|
|
240
|
+
* Remove a field from a schema definition (keeps database column).
|
|
241
|
+
*/
|
|
242
|
+
app.delete("/utils/schema-diff/field/schema/:collection/:field", adminOnly, async (req, res, next) => {
|
|
243
|
+
try {
|
|
244
|
+
const { collection, field } = req.params;
|
|
245
|
+
const schemaDef = await schemaManager.getSchemaDefinition(collection);
|
|
246
|
+
if (!schemaDef) {
|
|
247
|
+
throw new APIError(`Collection '${collection}' not found in schema definitions`, 404);
|
|
248
|
+
}
|
|
249
|
+
if (!schemaDef.fields || !schemaDef.fields[field]) {
|
|
250
|
+
throw new APIError(`Field '${field}' not found in schema for '${collection}'`, 404);
|
|
251
|
+
}
|
|
252
|
+
// Remove the field from schema
|
|
253
|
+
const updatedSchema = { ...schemaDef };
|
|
254
|
+
const updatedFields = { ...updatedSchema.fields };
|
|
255
|
+
delete updatedFields[field];
|
|
256
|
+
updatedSchema.fields = updatedFields;
|
|
257
|
+
// Persist updated schema
|
|
258
|
+
const db = getDatabase();
|
|
259
|
+
const { pgTable, text, jsonb, timestamp } = await import("drizzle-orm/pg-core");
|
|
260
|
+
const { eq } = await import("drizzle-orm");
|
|
261
|
+
const baasixSchemaDefinition = pgTable("baasix_SchemaDefinition", {
|
|
262
|
+
collectionName: text("collectionName").primaryKey().notNull(),
|
|
263
|
+
schema: jsonb("schema").notNull(),
|
|
264
|
+
createdAt: timestamp("createdAt", { withTimezone: true }).defaultNow(),
|
|
265
|
+
updatedAt: timestamp("updatedAt", { withTimezone: true }).defaultNow(),
|
|
266
|
+
});
|
|
267
|
+
await db
|
|
268
|
+
.update(baasixSchemaDefinition)
|
|
269
|
+
.set({ schema: updatedSchema, updatedAt: new Date() })
|
|
270
|
+
.where(eq(baasixSchemaDefinition.collectionName, collection));
|
|
271
|
+
// Update in-memory schema definition
|
|
272
|
+
// Re-initialize the model so in-memory state matches
|
|
273
|
+
await schemaManager.updateModel(collection, updatedSchema);
|
|
274
|
+
return res.status(200).json({
|
|
275
|
+
data: { message: `Field '${field}' removed from schema definition of '${collection}'` },
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
next(error);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
/**
|
|
283
|
+
* DELETE /utils/schema-diff/field/db/:collection/:column
|
|
284
|
+
* Drop a column from a database table (keeps schema field).
|
|
285
|
+
*/
|
|
286
|
+
app.delete("/utils/schema-diff/field/db/:collection/:column", adminOnly, async (req, res, next) => {
|
|
287
|
+
try {
|
|
288
|
+
const { collection, column } = req.params;
|
|
289
|
+
const sql = getSqlClient();
|
|
290
|
+
// Validate names
|
|
291
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(collection) || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(column)) {
|
|
292
|
+
throw new APIError("Invalid table or column name", 400);
|
|
293
|
+
}
|
|
294
|
+
// Verify column exists
|
|
295
|
+
const colExists = await sql `
|
|
296
|
+
SELECT EXISTS (
|
|
297
|
+
SELECT FROM information_schema.columns
|
|
298
|
+
WHERE table_schema = 'public'
|
|
299
|
+
AND table_name = ${collection}
|
|
300
|
+
AND column_name = ${column}
|
|
301
|
+
)
|
|
302
|
+
`;
|
|
303
|
+
if (!colExists[0].exists) {
|
|
304
|
+
throw new APIError(`Column '${column}' not found in table '${collection}'`, 404);
|
|
305
|
+
}
|
|
306
|
+
await sql.unsafe(`ALTER TABLE "${collection}" DROP COLUMN IF EXISTS "${column}" CASCADE`);
|
|
307
|
+
return res.status(200).json({
|
|
308
|
+
data: { message: `Column '${column}' dropped from table '${collection}'` },
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
next(error);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
export default {
|
|
317
|
+
id: "schema-diff",
|
|
318
|
+
handler: registerEndpoint,
|
|
319
|
+
};
|
|
320
|
+
//# sourceMappingURL=schema-diff.route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-diff.route.js","sourceRoot":"","sources":["../../baasix/routes/schema-diff.route.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Transaction } from '../utils/db.js';
|
|
1
2
|
import type { QueryOptions as BaseQueryOptions, ServiceParams as BaseServiceParams, OperationOptions, ReadResult } from '../types/index.js';
|
|
2
3
|
export type QueryOptions = BaseQueryOptions;
|
|
3
4
|
export type ServiceParams = BaseServiceParams;
|
|
@@ -131,7 +132,7 @@ export declare class ItemsService {
|
|
|
131
132
|
/**
|
|
132
133
|
* Read records by query
|
|
133
134
|
*/
|
|
134
|
-
readByQuery(query?: QueryOptions, bypassPermissions?: boolean): Promise<ReadResult>;
|
|
135
|
+
readByQuery(query?: QueryOptions, bypassPermissions?: boolean, transaction?: Transaction): Promise<ReadResult>;
|
|
135
136
|
/**
|
|
136
137
|
* Execute aggregate query
|
|
137
138
|
*/
|
|
@@ -139,7 +140,7 @@ export declare class ItemsService {
|
|
|
139
140
|
/**
|
|
140
141
|
* Read a single record by ID
|
|
141
142
|
*/
|
|
142
|
-
readOne(id: string | number, query?: QueryOptions, bypassPermissions?: boolean): Promise<any>;
|
|
143
|
+
readOne(id: string | number, query?: QueryOptions, bypassPermissions?: boolean, transaction?: Transaction): Promise<any>;
|
|
143
144
|
/**
|
|
144
145
|
* Create a new record
|
|
145
146
|
* Uses createOneCore for the transactional logic and executes after hooks after commit
|
|
@@ -242,11 +243,11 @@ export declare class ItemsService {
|
|
|
242
243
|
/**
|
|
243
244
|
* Alias for readByQuery
|
|
244
245
|
*/
|
|
245
|
-
list(query?: QueryOptions, bypassPermissions?: boolean): Promise<ReadResult>;
|
|
246
|
+
list(query?: QueryOptions, bypassPermissions?: boolean, transaction?: Transaction): Promise<ReadResult>;
|
|
246
247
|
/**
|
|
247
248
|
* Alias for readOne
|
|
248
249
|
*/
|
|
249
|
-
read(id: string | number, query?: QueryOptions, bypassPermissions?: boolean): Promise<any>;
|
|
250
|
+
read(id: string | number, query?: QueryOptions, bypassPermissions?: boolean, transaction?: Transaction): Promise<any>;
|
|
250
251
|
/**
|
|
251
252
|
* Alias for createOne
|
|
252
253
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ItemsService.d.ts","sourceRoot":"","sources":["../../baasix/services/ItemsService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ItemsService.d.ts","sourceRoot":"","sources":["../../baasix/services/ItemsService.ts"],"names":[],"mappings":"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"}
|
|
@@ -833,7 +833,8 @@ export class ItemsService {
|
|
|
833
833
|
/**
|
|
834
834
|
* Read with two-query approach for HasMany sorting
|
|
835
835
|
*/
|
|
836
|
-
async readWithHasManyHandling(query, whereClause, orderByClause, processedIncludes, limit, offset, isAdmin, bypassPermissions, filterJoins = []) {
|
|
836
|
+
async readWithHasManyHandling(query, whereClause, orderByClause, processedIncludes, limit, offset, isAdmin, bypassPermissions, filterJoins = [], transaction) {
|
|
837
|
+
const dbClient = transaction || db;
|
|
837
838
|
console.log('[ItemsService] Using Drizzle query builder for HasMany sorting/filtering');
|
|
838
839
|
// STEP 1: Build ID query using PostgreSQL DISTINCT ON for proper deduplication
|
|
839
840
|
// DISTINCT ON ensures we get unique IDs BEFORE applying LIMIT, not after
|
|
@@ -889,7 +890,7 @@ export class ItemsService {
|
|
|
889
890
|
? `DISTINCT ON (${distinctOnFields.join(', ')}) ${pkField}`
|
|
890
891
|
: pkField;
|
|
891
892
|
console.log(`[ItemsService] Using DISTINCT ON with fields: ${distinctOnFields.join(', ')}`);
|
|
892
|
-
let idQuery =
|
|
893
|
+
let idQuery = dbClient
|
|
893
894
|
.select({ [this.primaryKey]: sql.raw(distinctOnClause) })
|
|
894
895
|
.from(this.table)
|
|
895
896
|
.$dynamic();
|
|
@@ -978,7 +979,7 @@ export class ItemsService {
|
|
|
978
979
|
}
|
|
979
980
|
const { selectColumns } = buildSelectWithJoins(this.table, directFields, processedIncludes // Use original includes with separate: true for HasMany
|
|
980
981
|
);
|
|
981
|
-
let fullQuery =
|
|
982
|
+
let fullQuery = dbClient.select(selectColumns).from(this.table);
|
|
982
983
|
// Apply joins (only BelongsTo/HasOne, not HasMany)
|
|
983
984
|
for (const include of processedIncludes) {
|
|
984
985
|
if (include.separate)
|
|
@@ -998,7 +999,7 @@ export class ItemsService {
|
|
|
998
999
|
let finalRecords = records;
|
|
999
1000
|
if (hasSeparateQueries(processedIncludes)) {
|
|
1000
1001
|
// This handles both nesting joined relations and loading separate ones
|
|
1001
|
-
finalRecords = await loadSeparateRelations(
|
|
1002
|
+
finalRecords = await loadSeparateRelations(dbClient, records, processedIncludes, this.collection);
|
|
1002
1003
|
}
|
|
1003
1004
|
else {
|
|
1004
1005
|
// No separate queries, but we still need to nest joined BelongsTo/HasOne relations
|
|
@@ -1008,7 +1009,7 @@ export class ItemsService {
|
|
|
1008
1009
|
const recordMap = new Map(finalRecords.map(r => [r[this.primaryKey], r]));
|
|
1009
1010
|
const orderedRecords = ids.map(id => recordMap.get(id)).filter(r => r != null);
|
|
1010
1011
|
// Get total count using Drizzle query builder (same joins as ID query)
|
|
1011
|
-
let countQuery =
|
|
1012
|
+
let countQuery = dbClient
|
|
1012
1013
|
.select({ count: sql `COUNT(DISTINCT ${this.getPrimaryKeyColumn()})`.mapWith(Number) })
|
|
1013
1014
|
.from(this.table)
|
|
1014
1015
|
.$dynamic();
|
|
@@ -1085,15 +1086,16 @@ export class ItemsService {
|
|
|
1085
1086
|
/**
|
|
1086
1087
|
* Read records by query
|
|
1087
1088
|
*/
|
|
1088
|
-
async readByQuery(query = {}, bypassPermissions = false) {
|
|
1089
|
+
async readByQuery(query = {}, bypassPermissions = false, transaction) {
|
|
1090
|
+
const dbClient = transaction || db;
|
|
1089
1091
|
// Execute before-read hooks
|
|
1090
|
-
let hookData = await hooksManager.executeHooks(this.collection, 'items.read', this.accountability, { query });
|
|
1092
|
+
let hookData = await hooksManager.executeHooks(this.collection, 'items.read', this.accountability, { query, transaction });
|
|
1091
1093
|
const modifiedQuery = hookData.query;
|
|
1092
1094
|
try {
|
|
1093
1095
|
const isAdmin = await this.isAdministrator();
|
|
1094
1096
|
// Check if this is an aggregate query
|
|
1095
1097
|
if (modifiedQuery.aggregate) {
|
|
1096
|
-
return await this.executeAggregateQuery(modifiedQuery, isAdmin, bypassPermissions);
|
|
1098
|
+
return await this.executeAggregateQuery(modifiedQuery, isAdmin, bypassPermissions, transaction);
|
|
1097
1099
|
}
|
|
1098
1100
|
// Build query components
|
|
1099
1101
|
const { whereClause, orderByClause, selectColumns, joins, processedIncludes, limit, offset, filterJoins = [], userRequestedFields = [], userRequestedIncludes = [] } = await this.buildQuery(modifiedQuery, {
|
|
@@ -1130,7 +1132,7 @@ export class ItemsService {
|
|
|
1130
1132
|
// This matches Sequelize's approach of using a two-query pattern
|
|
1131
1133
|
if (needsHasManyHandling || hasFilterJoins) {
|
|
1132
1134
|
console.log(`[ItemsService] Using readWithHasManyHandling for ${needsHasManyHandling ? 'HasMany sorting' : 'HasMany filtering'}`);
|
|
1133
|
-
return await this.readWithHasManyHandling(modifiedQuery, whereClause, orderByClause, processedIncludes, limit, offset, isAdmin, bypassPermissions, filterJoins);
|
|
1135
|
+
return await this.readWithHasManyHandling(modifiedQuery, whereClause, orderByClause, processedIncludes, limit, offset, isAdmin, bypassPermissions, filterJoins, transaction);
|
|
1134
1136
|
}
|
|
1135
1137
|
// Build base query
|
|
1136
1138
|
console.log(`[ItemsService.readByQuery] Building base query for ${this.collection}, selectColumns has ${Object.keys(selectColumns).length} keys`);
|
|
@@ -1138,7 +1140,7 @@ export class ItemsService {
|
|
|
1138
1140
|
console.error(`[ItemsService.readByQuery] ERROR: selectColumns is EMPTY for ${this.collection}! This will cause SQL syntax error.`);
|
|
1139
1141
|
console.error(`[ItemsService.readByQuery] query:`, query);
|
|
1140
1142
|
}
|
|
1141
|
-
let baseQuery =
|
|
1143
|
+
let baseQuery = dbClient.select(selectColumns).from(this.table);
|
|
1142
1144
|
// Apply filterJoins using Drizzle's query builder
|
|
1143
1145
|
// FilterJoins are created when filtering by relation paths (e.g., "userRoles.role.name")
|
|
1144
1146
|
if (hasFilterJoins) {
|
|
@@ -1206,7 +1208,7 @@ export class ItemsService {
|
|
|
1206
1208
|
// Load separate relations (HasMany, BelongsToMany) and nest joined relations
|
|
1207
1209
|
let processedRecords = records;
|
|
1208
1210
|
if (hasSeparateQueries(processedIncludes)) {
|
|
1209
|
-
processedRecords = await loadSeparateRelations(
|
|
1211
|
+
processedRecords = await loadSeparateRelations(dbClient, records, processedIncludes, this.collection);
|
|
1210
1212
|
}
|
|
1211
1213
|
else {
|
|
1212
1214
|
// No separate queries, but we still need to nest joined BelongsTo/HasOne relations
|
|
@@ -1219,7 +1221,7 @@ export class ItemsService {
|
|
|
1219
1221
|
console.error(`[ItemsService.readByQuery] Primary key name:`, this.primaryKey);
|
|
1220
1222
|
console.error(`[ItemsService.readByQuery] Available columns:`, Object.keys(this.table).filter(k => !k.startsWith('_')));
|
|
1221
1223
|
}
|
|
1222
|
-
let countQuery =
|
|
1224
|
+
let countQuery = dbClient.select({ count: sql `COUNT(DISTINCT ${primaryKeyColumn})` }).from(this.table);
|
|
1223
1225
|
// Apply same joins and where for count
|
|
1224
1226
|
if (hasFilterJoins) {
|
|
1225
1227
|
// Use filterJoins for count query as well
|
|
@@ -1266,7 +1268,7 @@ export class ItemsService {
|
|
|
1266
1268
|
// Sanitize auto-added fields (remove fields user didn't request)
|
|
1267
1269
|
const sanitizedRecords = this.sanitizeAutoAddedFields(strippedRecords, userRequestedFields, userRequestedIncludes);
|
|
1268
1270
|
// Execute after-read hooks
|
|
1269
|
-
hookData = await hooksManager.executeHooks(this.collection, 'items.read.after', this.accountability, { query: modifiedQuery, result: { data: sanitizedRecords, totalCount } });
|
|
1271
|
+
hookData = await hooksManager.executeHooks(this.collection, 'items.read.after', this.accountability, { query: modifiedQuery, result: { data: sanitizedRecords, totalCount }, transaction });
|
|
1270
1272
|
return hookData.result;
|
|
1271
1273
|
}
|
|
1272
1274
|
catch (error) {
|
|
@@ -1277,7 +1279,8 @@ export class ItemsService {
|
|
|
1277
1279
|
/**
|
|
1278
1280
|
* Execute aggregate query
|
|
1279
1281
|
*/
|
|
1280
|
-
async executeAggregateQuery(query, isAdmin, bypassPermissions) {
|
|
1282
|
+
async executeAggregateQuery(query, isAdmin, bypassPermissions, transaction) {
|
|
1283
|
+
const dbClient = transaction || db;
|
|
1281
1284
|
const { aggregate, groupBy = [], filter = {}, sort, limit: queryLimit, page: queryPage } = query;
|
|
1282
1285
|
if (!aggregate) {
|
|
1283
1286
|
throw new APIError('Aggregate query requires aggregate parameter', 400);
|
|
@@ -1406,7 +1409,7 @@ export class ItemsService {
|
|
|
1406
1409
|
let results;
|
|
1407
1410
|
if (allJoins.length > 0) {
|
|
1408
1411
|
// Build aggregate query using Drizzle query builder
|
|
1409
|
-
let aggregateQuery =
|
|
1412
|
+
let aggregateQuery = dbClient.select(selectObj).from(this.table).$dynamic();
|
|
1410
1413
|
// Apply joins (same as filterJoins)
|
|
1411
1414
|
for (const join of allJoins) {
|
|
1412
1415
|
// Create aliased table with the exact alias
|
|
@@ -1461,7 +1464,7 @@ export class ItemsService {
|
|
|
1461
1464
|
}
|
|
1462
1465
|
else {
|
|
1463
1466
|
// No relation joins - use standard Drizzle API
|
|
1464
|
-
let aggregateQuery =
|
|
1467
|
+
let aggregateQuery = dbClient.select(selectObj).from(this.table).$dynamic();
|
|
1465
1468
|
if (whereClause) {
|
|
1466
1469
|
aggregateQuery = aggregateQuery.where(whereClause);
|
|
1467
1470
|
}
|
|
@@ -1513,7 +1516,7 @@ export class ItemsService {
|
|
|
1513
1516
|
const groupExpr = buildGroupByExpressions([groupField], undefined, pathToAliasMap, this.collection)[0];
|
|
1514
1517
|
countSelect[groupField] = groupExpr;
|
|
1515
1518
|
}
|
|
1516
|
-
let countQuery =
|
|
1519
|
+
let countQuery = dbClient.select(countSelect).from(this.table).$dynamic();
|
|
1517
1520
|
// Apply same joins
|
|
1518
1521
|
for (const join of allJoins) {
|
|
1519
1522
|
const aliasedTable = alias(join.table, join.alias);
|
|
@@ -1542,13 +1545,14 @@ export class ItemsService {
|
|
|
1542
1545
|
/**
|
|
1543
1546
|
* Read a single record by ID
|
|
1544
1547
|
*/
|
|
1545
|
-
async readOne(id, query = {}, bypassPermissions = false) {
|
|
1548
|
+
async readOne(id, query = {}, bypassPermissions = false, transaction) {
|
|
1549
|
+
const dbClient = transaction || db;
|
|
1546
1550
|
const parsedId = this.parseId(id);
|
|
1547
1551
|
if (!parsedId) {
|
|
1548
1552
|
throw new APIError('Invalid ID', 400);
|
|
1549
1553
|
}
|
|
1550
1554
|
// Execute before-read-one hooks
|
|
1551
|
-
let hookData = await hooksManager.executeHooks(this.collection, 'items.read.one', this.accountability, { id: parsedId, query });
|
|
1555
|
+
let hookData = await hooksManager.executeHooks(this.collection, 'items.read.one', this.accountability, { id: parsedId, query, transaction });
|
|
1552
1556
|
try {
|
|
1553
1557
|
const isAdmin = await this.isAdministrator();
|
|
1554
1558
|
// Build query with ID filter
|
|
@@ -1559,7 +1563,7 @@ export class ItemsService {
|
|
|
1559
1563
|
idFilter: parsedId
|
|
1560
1564
|
});
|
|
1561
1565
|
// Build base query
|
|
1562
|
-
let baseQuery =
|
|
1566
|
+
let baseQuery = dbClient.select(selectColumns).from(this.table);
|
|
1563
1567
|
// Apply filterJoins first (these come from relation path filters in WHERE clause)
|
|
1564
1568
|
// use Drizzle query builder methods instead of raw SQL
|
|
1565
1569
|
if (filterJoins && filterJoins.length > 0) {
|
|
@@ -1592,7 +1596,7 @@ export class ItemsService {
|
|
|
1592
1596
|
// Load separate relations and nest joined relations
|
|
1593
1597
|
let finalRecords = records;
|
|
1594
1598
|
if (hasSeparateQueries(processedIncludes)) {
|
|
1595
|
-
finalRecords = await loadSeparateRelations(
|
|
1599
|
+
finalRecords = await loadSeparateRelations(dbClient, records, processedIncludes, this.collection);
|
|
1596
1600
|
}
|
|
1597
1601
|
else {
|
|
1598
1602
|
// No separate queries, but we still need to nest joined BelongsTo/HasOne relations
|
|
@@ -1602,7 +1606,7 @@ export class ItemsService {
|
|
|
1602
1606
|
// Strip hidden fields from the document
|
|
1603
1607
|
const strippedDocument = fieldUtils.stripHiddenFields(this.collection, document);
|
|
1604
1608
|
// Execute after-read-one hooks
|
|
1605
|
-
hookData = await hooksManager.executeHooks(this.collection, 'items.read.one.after', this.accountability, { id: parsedId, query, document: strippedDocument });
|
|
1609
|
+
hookData = await hooksManager.executeHooks(this.collection, 'items.read.one.after', this.accountability, { id: parsedId, query, document: strippedDocument, transaction });
|
|
1606
1610
|
return hookData.document;
|
|
1607
1611
|
}
|
|
1608
1612
|
catch (error) {
|
|
@@ -2409,14 +2413,14 @@ export class ItemsService {
|
|
|
2409
2413
|
/**
|
|
2410
2414
|
* Alias for readByQuery
|
|
2411
2415
|
*/
|
|
2412
|
-
async list(query = {}, bypassPermissions = false) {
|
|
2413
|
-
return this.readByQuery(query, bypassPermissions);
|
|
2416
|
+
async list(query = {}, bypassPermissions = false, transaction) {
|
|
2417
|
+
return this.readByQuery(query, bypassPermissions, transaction);
|
|
2414
2418
|
}
|
|
2415
2419
|
/**
|
|
2416
2420
|
* Alias for readOne
|
|
2417
2421
|
*/
|
|
2418
|
-
async read(id, query = {}, bypassPermissions = false) {
|
|
2419
|
-
return this.readOne(id, query, bypassPermissions);
|
|
2422
|
+
async read(id, query = {}, bypassPermissions = false, transaction) {
|
|
2423
|
+
return this.readOne(id, query, bypassPermissions, transaction);
|
|
2420
2424
|
}
|
|
2421
2425
|
/**
|
|
2422
2426
|
* Alias for createOne
|