@tiddlywiki/mws 0.0.12 → 0.0.14
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/.env-cmdrc +5 -0
- package/README.md +3 -1
- package/dist/server.d.ts +923 -0
- package/dist/server.js +1763 -1415
- package/dist/server.js.map +1 -1
- package/editions/mws-docs/tiddlers/$__StoryList.tid +3 -3
- package/editions/mws-docs/tiddlers/MWS and SQLite.md +11 -0
- package/editions/mws-docs/tiddlers/Request Server.md +20 -0
- package/editions/mws-docs/tiddlers/Request Server.md.meta +5 -0
- package/package.json +17 -10
- package/prisma/schema.prisma +1 -0
- package/react-user-mgmt/package-lock.json +76 -321
- package/react-user-mgmt/package.json +2 -5
- package/react-user-mgmt/public/chunk-YALWN5OT.js +2 -0
- package/react-user-mgmt/public/chunk-YALWN5OT.js.map +7 -0
- package/react-user-mgmt/public/esm-QKDFEP3B.js +3 -0
- package/react-user-mgmt/public/esm-QKDFEP3B.js.map +7 -0
- package/react-user-mgmt/public/index.html +1 -26
- package/react-user-mgmt/public/main.js +68 -109
- package/react-user-mgmt/public/main.js.map +4 -4
- package/react-user-mgmt/public/stats.css +597 -0
- package/react-user-mgmt/public/stats.html +59 -0
- package/react-user-mgmt/public/stats.js +2013 -0
- package/react-user-mgmt/public/stats.json +1 -0
- package/react-user-mgmt/public/stats.svg +4 -0
- package/react-user-mgmt/tsconfig.json +3 -0
- package/dist/server.cjs +0 -16677
- package/dist/server.cjs.map +0 -1
- package/src/StateObject.ts +0 -316
- package/src/commander.ts +0 -417
- package/src/commands/divider.ts +0 -20
- package/src/commands/index.ts +0 -24
- package/src/commands/mws-init-store.ts +0 -77
- package/src/commands/mws-listen.ts +0 -136
- package/src/commands/mws-load-archive.ts +0 -281
- package/src/commands/mws-load-plugin-bags.ts +0 -151
- package/src/commands/mws-load-tiddlers.ts +0 -37
- package/src/commands/mws-load-wiki-folder.ts +0 -124
- package/src/commands/mws-render-tiddlywiki5.ts +0 -55
- package/src/commands/mws-save-archive.ts +0 -111
- package/src/commands/mws-save-tiddler-text.ts +0 -33
- package/src/commands/tests-complete.ts +0 -21
- package/src/global.ts +0 -247
- package/src/routes/TiddlerStore.ts +0 -722
- package/src/routes/bag-file-server.ts +0 -340
- package/src/routes/cache.ts +0 -25
- package/src/routes/index.ts +0 -138
- package/src/routes/managers/index.ts +0 -17
- package/src/routes/managers/manager-recipes.ts +0 -438
- package/src/routes/managers/manager-users.ts +0 -201
- package/src/routes/managers/router-tiddlers.ts +0 -313
- package/src/routes/router.ts +0 -429
- package/src/routes/services/PasswordService.ts +0 -178
- package/src/routes/services/attachments.ts +0 -259
- package/src/routes/services/sessions.ts +0 -166
- package/src/routes/tw-test.ts +0 -65
- package/src/server.ts +0 -151
- package/src/setupDevServer.ts +0 -71
- package/src/streamer.ts +0 -430
- package/src/tiddlywiki.ts +0 -94
- package/src/utils/data-checks.ts +0 -172
- package/src/utils/index.ts +0 -4
- package/src/utils/rootRoute.ts +0 -237
- package/src/utils/utils.ts +0 -403
- package/src/utils/zodAssert.ts +0 -269
- /package/prisma/{migrations/notes.md → notes.md} +0 -0
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import { StateObject } from "../StateObject";
|
|
2
|
-
import { TiddlerStore } from "./TiddlerStore";
|
|
3
|
-
import { resolve } from "path";
|
|
4
|
-
import { createWriteStream, readFileSync } from "fs";
|
|
5
|
-
import sjcl from "sjcl";
|
|
6
|
-
import { createHash } from "crypto";
|
|
7
|
-
import { TiddlerFields } from "./services/attachments";
|
|
8
|
-
import { UserError } from "../utils";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export class TiddlerServer extends TiddlerStore {
|
|
12
|
-
|
|
13
|
-
constructor(
|
|
14
|
-
protected state: StateObject,
|
|
15
|
-
prisma: PrismaTxnClient
|
|
16
|
-
) {
|
|
17
|
-
super(state.commander, prisma);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
private toTiddlyWebJson(info: { bag_name: string; tiddler_id: number; tiddler: TiddlerFields }) {
|
|
21
|
-
const output: any = { fields: {} };
|
|
22
|
-
const knownFields = [
|
|
23
|
-
"bag", "created", "creator", "modified", "modifier", "permissions", "recipe", "revision", "tags", "text", "title", "type", "uri"
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
each(info.tiddler, (field, name) => {
|
|
27
|
-
if (knownFields.indexOf(name) !== -1) {
|
|
28
|
-
output[name] = field;
|
|
29
|
-
} else {
|
|
30
|
-
output.fields[name] = field;
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
output.type = info.tiddler.type || "text/vnd.tiddlywiki";
|
|
34
|
-
|
|
35
|
-
output.revision = `${info.tiddler_id}`;
|
|
36
|
-
output.bag = info.bag_name;
|
|
37
|
-
return output;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** If neither bag_name nor bag_id are specified, the fallback will be sent. */
|
|
41
|
-
async sendBagTiddler({ state, bag_name, bag_id, title }: {
|
|
42
|
-
state: StateObject;
|
|
43
|
-
bag_name?: PrismaField<"Bags", "bag_name">;
|
|
44
|
-
bag_id?: PrismaField<"Bags", "bag_id">;
|
|
45
|
-
title: PrismaField<"Tiddlers", "title">;
|
|
46
|
-
}) {
|
|
47
|
-
|
|
48
|
-
const tiddlerInfo = (bag_id || bag_name) && await this.getBagTiddler({ bag_name, bag_id, title });
|
|
49
|
-
|
|
50
|
-
if (!tiddlerInfo || !tiddlerInfo.tiddler) {
|
|
51
|
-
// Redirect to fallback URL if tiddler not found
|
|
52
|
-
const fallback = state.queryParams.fallback?.[0];
|
|
53
|
-
if (fallback) {
|
|
54
|
-
// await state.pushStream(fallback);
|
|
55
|
-
return state.redirect(fallback);
|
|
56
|
-
} else {
|
|
57
|
-
return state.sendEmpty(404, { "x-reason": "tiddler not found (no fallback)" });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// If application/json is requested then this is an API request, and gets the response in JSON
|
|
63
|
-
if (state.headers.accept && state.headers.accept.indexOf("application/json") !== -1) {
|
|
64
|
-
|
|
65
|
-
return state.sendResponse(200, {
|
|
66
|
-
"Etag": state.makeTiddlerEtag(tiddlerInfo),
|
|
67
|
-
"Content-Type": "application/json",
|
|
68
|
-
"X-Revision-Number": tiddlerInfo.tiddler_id,
|
|
69
|
-
"X-Bag-Name": tiddlerInfo.bag_name,
|
|
70
|
-
}, JSON.stringify(tiddlerInfo.tiddler), "utf8");
|
|
71
|
-
|
|
72
|
-
} else {
|
|
73
|
-
|
|
74
|
-
// This is not a JSON API request, we should return the raw tiddler content
|
|
75
|
-
const result = await this.getBagTiddlerStream(title, tiddlerInfo.bag_name);
|
|
76
|
-
if (!result || !result.stream) return state.sendEmpty(404);
|
|
77
|
-
|
|
78
|
-
return state.sendStream(200, {
|
|
79
|
-
"Etag": state.makeTiddlerEtag(result),
|
|
80
|
-
"Content-Type": result.type
|
|
81
|
-
}, result.stream);
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Process an incoming new multipart/form-data stream. Options include:
|
|
90
|
-
*
|
|
91
|
-
* @param {Object} options
|
|
92
|
-
* @param {SqlTiddlerStore} options.store
|
|
93
|
-
* @param {ServerState} options.state
|
|
94
|
-
* @param {ServerResponse} options.response
|
|
95
|
-
* @param {string} options.bag_name
|
|
96
|
-
* @param {function} options.callback
|
|
97
|
-
*/
|
|
98
|
-
async processIncomingStream(
|
|
99
|
-
bag_name: PrismaField<"Bags", "bag_name">
|
|
100
|
-
): Promise<string[]> {
|
|
101
|
-
|
|
102
|
-
// Process the incoming data
|
|
103
|
-
const inboxName = new Date().toISOString().replace(/:/g, "-");
|
|
104
|
-
const inboxPath = resolve(this.storePath, "inbox", inboxName);
|
|
105
|
-
createDirectory(inboxPath);
|
|
106
|
-
let fileStream: { write: (arg0: any) => void; end: () => void; } | null = null; // Current file being written
|
|
107
|
-
let hash: { update: (arg0: any) => void; finalize: () => any; } | null = null; // Accumulating hash of current part
|
|
108
|
-
let length = 0; // Accumulating length of current part
|
|
109
|
-
const parts: any[] = []; // Array of {name:, headers:, value:, hash:} and/or {name:, filename:, headers:, inboxFilename:, hash:}
|
|
110
|
-
|
|
111
|
-
await this.state.readMultipartData({
|
|
112
|
-
cbPartStart: function (headers, name, filename) {
|
|
113
|
-
const part: any = {
|
|
114
|
-
name: name,
|
|
115
|
-
filename: filename,
|
|
116
|
-
headers: headers
|
|
117
|
-
};
|
|
118
|
-
if (filename) {
|
|
119
|
-
const inboxFilename = (parts.length).toString();
|
|
120
|
-
part.inboxFilename = resolve(inboxPath, inboxFilename);
|
|
121
|
-
fileStream = createWriteStream(part.inboxFilename);
|
|
122
|
-
} else {
|
|
123
|
-
part.value = "";
|
|
124
|
-
}
|
|
125
|
-
hash = new sjcl.hash.sha256();
|
|
126
|
-
length = 0;
|
|
127
|
-
parts.push(part);
|
|
128
|
-
},
|
|
129
|
-
cbPartChunk: function (chunk) {
|
|
130
|
-
if (fileStream) {
|
|
131
|
-
fileStream.write(chunk);
|
|
132
|
-
} else {
|
|
133
|
-
parts[parts.length - 1].value += chunk;
|
|
134
|
-
}
|
|
135
|
-
length = length + chunk.length;
|
|
136
|
-
hash!.update(chunk);
|
|
137
|
-
},
|
|
138
|
-
cbPartEnd: function () {
|
|
139
|
-
if (fileStream) {
|
|
140
|
-
fileStream.end();
|
|
141
|
-
}
|
|
142
|
-
fileStream = null;
|
|
143
|
-
parts[parts.length - 1].hash = sjcl.codec.hex.fromBits(hash!.finalize()).slice(0, 64).toString();
|
|
144
|
-
hash = null;
|
|
145
|
-
},
|
|
146
|
-
// if an error is given here, it will also be thrown in the promise
|
|
147
|
-
cbFinished: (err) => { }
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const partFile = parts.find(part => part.name === "file-to-upload" && !!part.filename);
|
|
151
|
-
if (!partFile) {
|
|
152
|
-
throw await this.state.sendSimple(400, "Missing file to upload");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const type = partFile.headers["content-type"];
|
|
156
|
-
const tiddlerFields = { title: partFile.filename, type };
|
|
157
|
-
|
|
158
|
-
for (const part of parts) {
|
|
159
|
-
const tiddlerFieldPrefix = "tiddler-field-";
|
|
160
|
-
if (part.name.startsWith(tiddlerFieldPrefix)) {
|
|
161
|
-
(tiddlerFields as any)[part.name.slice(tiddlerFieldPrefix.length)] = part.value.trim();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
await this.saveBagTiddlerWithAttachment(tiddlerFields, bag_name, {
|
|
166
|
-
filepath: partFile.inboxFilename,
|
|
167
|
-
type: type,
|
|
168
|
-
hash: partFile.hash
|
|
169
|
-
} as any).then(() => {
|
|
170
|
-
deleteDirectory(inboxPath);
|
|
171
|
-
return [tiddlerFields.title];
|
|
172
|
-
}, err => {
|
|
173
|
-
throw err;
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
return parts;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async getRecipeTiddlers(recipe_name: PrismaField<"Recipes", "recipe_name">) {
|
|
180
|
-
// Get the recipe name from the parameters
|
|
181
|
-
const bagTiddlers = await this.getRecipeTiddlersByBag(recipe_name);
|
|
182
|
-
|
|
183
|
-
bagTiddlers.sort((a, b) => a.position - b.position);
|
|
184
|
-
|
|
185
|
-
return Array.from(new Map(bagTiddlers.flatMap(bag =>
|
|
186
|
-
bag.tiddlers.map(tiddler => [tiddler.title, {
|
|
187
|
-
title: tiddler.title,
|
|
188
|
-
tiddler_id: tiddler.tiddler_id,
|
|
189
|
-
is_deleted: tiddler.is_deleted,
|
|
190
|
-
is_plugin: false,
|
|
191
|
-
bag_name: bag.bag_name,
|
|
192
|
-
bag_id: bag.bag_id
|
|
193
|
-
}])
|
|
194
|
-
)).values());
|
|
195
|
-
|
|
196
|
-
// SELECT title, tiddler_id, is_deleted, bag_name
|
|
197
|
-
// FROM (
|
|
198
|
-
// SELECT t.title, t.tiddler_id, t.is_deleted, b.bag_name, MAX(rb.position) AS position
|
|
199
|
-
// FROM bags AS b
|
|
200
|
-
// INNER JOIN recipe_bags AS rb ON b.bag_id = rb.bag_id
|
|
201
|
-
// INNER JOIN tiddlers AS t ON b.bag_id = t.bag_id
|
|
202
|
-
// WHERE rb.recipe_id = $recipe_id
|
|
203
|
-
// ${options.include_deleted ? "" : "AND t.is_deleted = FALSE"}
|
|
204
|
-
// ${options.last_known_tiddler_id ? "AND tiddler_id > $last_known_tiddler_id" : ""}
|
|
205
|
-
// GROUP BY t.title
|
|
206
|
-
// ORDER BY t.title, tiddler_id DESC
|
|
207
|
-
// ${options.limit ? "LIMIT $limit" : ""}
|
|
208
|
-
// )
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
async serveIndexFile(recipe_name: PrismaField<"Recipes", "recipe_name">) {
|
|
212
|
-
const state = this.state;
|
|
213
|
-
|
|
214
|
-
// Check request is valid
|
|
215
|
-
if (!recipe_name) {
|
|
216
|
-
return state.sendEmpty(404);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// const bagTiddlers = await this.getRecipeTiddlersByBag(recipe_name);
|
|
220
|
-
const bagTiddlers = await this.prisma.recipe_bags.findMany({
|
|
221
|
-
where: { recipe: { recipe_name } },
|
|
222
|
-
select: {
|
|
223
|
-
position: true,
|
|
224
|
-
bag: {
|
|
225
|
-
select: {
|
|
226
|
-
bag_id: true,
|
|
227
|
-
bag_name: true,
|
|
228
|
-
is_plugin: true,
|
|
229
|
-
tiddlers: {
|
|
230
|
-
select: {
|
|
231
|
-
title: true,
|
|
232
|
-
tiddler_id: true,
|
|
233
|
-
is_deleted: true,
|
|
234
|
-
attachment_hash: true,
|
|
235
|
-
fields: true,
|
|
236
|
-
},
|
|
237
|
-
where: {
|
|
238
|
-
is_deleted: false,
|
|
239
|
-
},
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
bagTiddlers.sort((a, b) => a.position - b.position);
|
|
247
|
-
|
|
248
|
-
// const recipeTiddlers = await this.getRecipeTiddlers(recipe_name);
|
|
249
|
-
const recipeTiddlers = Array.from(new Map(bagTiddlers.flatMap(bag =>
|
|
250
|
-
bag.bag.tiddlers.map(tiddler => [tiddler.title, { bag: bag.bag, tiddler }])
|
|
251
|
-
)).values());
|
|
252
|
-
|
|
253
|
-
const template = readFileSync(resolve(this.state.config.wikiPath, "tiddlywiki5.html"), "utf8");
|
|
254
|
-
const hash = createHash('md5');
|
|
255
|
-
// Put everything into the hash that could change and invalidate the data that
|
|
256
|
-
// the browser already stored. The headers the data and the encoding.
|
|
257
|
-
hash.update(template);
|
|
258
|
-
hash.update(recipeTiddlers.map(e => e.tiddler.tiddler_id).join("|"));
|
|
259
|
-
const contentDigest = hash.digest("hex");
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
state.writeHead(200, {
|
|
263
|
-
"Content-Type": "text/html",
|
|
264
|
-
"Etag": '"' + contentDigest + '"',
|
|
265
|
-
"Cache-Control": "max-age=0, must-revalidate",
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
if (state.method === "HEAD") return state.end();
|
|
269
|
-
// Get the tiddlers in the recipe
|
|
270
|
-
// Render the template
|
|
271
|
-
|
|
272
|
-
// Splice in our tiddlers
|
|
273
|
-
var marker = `<` + `script class="tiddlywiki-tiddler-store" type="application/json">[`,
|
|
274
|
-
markerPos = template.indexOf(marker);
|
|
275
|
-
if (markerPos === -1) {
|
|
276
|
-
throw new Error("Cannot find tiddler store in template");
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
*
|
|
280
|
-
* @param {Record<string, string>} tiddlerFields
|
|
281
|
-
*/
|
|
282
|
-
function writeTiddler(tiddlerFields: Record<string, string>) {
|
|
283
|
-
state.write(JSON.stringify(tiddlerFields).replace(/</g, "\\u003c"));
|
|
284
|
-
state.write(",\n");
|
|
285
|
-
}
|
|
286
|
-
state.write(template.substring(0, markerPos + marker.length));
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
bagInfo: Record<string, string> = {},
|
|
291
|
-
revisionInfo: Record<string, string> = {};
|
|
292
|
-
|
|
293
|
-
for (const recipeTiddlerInfo of recipeTiddlers) {
|
|
294
|
-
// var result = await this.getRecipeTiddler(recipeTiddlerInfo.title, recipe_name);
|
|
295
|
-
// const { title, bag_id, tiddler_id } = recipeTiddlerInfo;
|
|
296
|
-
// var result = await this.getBagTiddler({ title, bag_id });
|
|
297
|
-
|
|
298
|
-
const { title, tiddler_id, attachment_hash, fields } = recipeTiddlerInfo.tiddler;
|
|
299
|
-
const { bag_name } = recipeTiddlerInfo.bag;
|
|
300
|
-
|
|
301
|
-
const tiddler = Object.fromEntries([
|
|
302
|
-
...fields.map(e => [e.field_name, e.field_value] as const),
|
|
303
|
-
["title", title]
|
|
304
|
-
]) as TiddlerFields;
|
|
305
|
-
|
|
306
|
-
const tiddler2 = this.attachService.processOutgoingTiddler({
|
|
307
|
-
tiddler, tiddler_id, attachment_hash, bag_name
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
bagInfo[title] = bag_name;
|
|
311
|
-
revisionInfo[title] = tiddler_id.toString();
|
|
312
|
-
writeTiddler(tiddler2);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
writeTiddler({
|
|
316
|
-
title: "$:/state/multiwikiclient/tiddlers/bag",
|
|
317
|
-
text: JSON.stringify(bagInfo),
|
|
318
|
-
type: "application/json"
|
|
319
|
-
});
|
|
320
|
-
writeTiddler({
|
|
321
|
-
title: "$:/state/multiwikiclient/tiddlers/revision",
|
|
322
|
-
text: JSON.stringify(revisionInfo),
|
|
323
|
-
type: "application/json"
|
|
324
|
-
});
|
|
325
|
-
writeTiddler({
|
|
326
|
-
title: "$:/config/multiwikiclient/recipe",
|
|
327
|
-
text: recipe_name
|
|
328
|
-
});
|
|
329
|
-
// get the latest tiddler id in the database. It doesn't have to be filtered.
|
|
330
|
-
const lastTiddlerId = await this.prisma.tiddlers.aggregate({ _max: { tiddler_id: true } });
|
|
331
|
-
writeTiddler({
|
|
332
|
-
title: "$:/state/multiwikiclient/recipe/last_tiddler_id",
|
|
333
|
-
text: (lastTiddlerId._max.tiddler_id ?? 0).toString()
|
|
334
|
-
});
|
|
335
|
-
state.write(template.substring(markerPos + marker.length))
|
|
336
|
-
// Finish response
|
|
337
|
-
return state.end();
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
}
|
package/src/routes/cache.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { ok } from "assert";
|
|
2
|
-
import { truthy } from "../utils";
|
|
3
|
-
import { Commander } from "../commander";
|
|
4
|
-
|
|
5
|
-
export async function startupCache(commander: Commander) {
|
|
6
|
-
const $tw = commander.$tw;
|
|
7
|
-
|
|
8
|
-
const bootTiddlers = $tw.loadTiddlersFromPath($tw.boot.bootPath).flatMap(file => file.tiddlers);
|
|
9
|
-
const coreTiddler = $tw.loadPluginFolder($tw.boot.corePath);
|
|
10
|
-
ok(coreTiddler);
|
|
11
|
-
|
|
12
|
-
// map ( title -> content )
|
|
13
|
-
const tiddlerMemoryCache = new Map<string, string>([
|
|
14
|
-
...bootTiddlers.map(e => e.title && [e.title, JSON.stringify(e)] as const).filter(truthy),
|
|
15
|
-
[coreTiddler.title as string, JSON.stringify(coreTiddler)] as const,
|
|
16
|
-
]);
|
|
17
|
-
|
|
18
|
-
// map ( title -> hash )
|
|
19
|
-
// tiddler cache files are named [hash].json
|
|
20
|
-
const tiddlerFileCache = new Map<string, string>();
|
|
21
|
-
|
|
22
|
-
return { tiddlerFileCache, tiddlerMemoryCache };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type CacheState = ART<typeof startupCache>;
|
package/src/routes/index.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { readdirSync, statSync } from "fs";
|
|
2
|
-
import { rootRoute, SiteConfig } from "./router";
|
|
3
|
-
import { ZodAssert } from "../utils";
|
|
4
|
-
import { Prisma, PrismaClient } from "@prisma/client";
|
|
5
|
-
import { ManagerRoutes } from "./managers";
|
|
6
|
-
import { TiddlerRouter } from "./managers/router-tiddlers";
|
|
7
|
-
|
|
8
|
-
declare global { const ENABLE_UNSAFE_PRISMA_ROUTE: any; }
|
|
9
|
-
|
|
10
|
-
export default async function RootRoute(root: rootRoute, config: SiteConfig) {
|
|
11
|
-
// TiddlerServer.defineRoutes(root);
|
|
12
|
-
TiddlerRouter.defineRoutes(root);
|
|
13
|
-
ManagerRoutes(root, config);
|
|
14
|
-
if (typeof ENABLE_UNSAFE_PRISMA_ROUTE !== "undefined")
|
|
15
|
-
root.defineRoute({
|
|
16
|
-
method: ["POST"],
|
|
17
|
-
path: /^\/prisma$/,
|
|
18
|
-
bodyFormat: "json",
|
|
19
|
-
}, async state => {
|
|
20
|
-
ZodAssert.data(state, z => z.object({
|
|
21
|
-
table: z.string(),
|
|
22
|
-
action: z.string(),
|
|
23
|
-
arg: z.any(),
|
|
24
|
-
}));
|
|
25
|
-
return await state.$transaction(async prisma => {
|
|
26
|
-
// DEV: this just lets the client directly call the database.
|
|
27
|
-
// TODO: it's just for dev purposes and will be removed later.
|
|
28
|
-
// DANGER: it circumvents all security and can totally rewrite the ACL.
|
|
29
|
-
const p: any = prisma;
|
|
30
|
-
const table = p[state.data.table];
|
|
31
|
-
if (!table) throw new Error(`No such table`);
|
|
32
|
-
const fn = table[state.data.action];
|
|
33
|
-
if (!fn) throw new Error(`No such table or action`);
|
|
34
|
-
console.log(state.data.arg);
|
|
35
|
-
return state.sendJSON(200, await fn.call(table, state.data.arg));
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
await importEsbuild(root);
|
|
41
|
-
}
|
|
42
|
-
async function importDir(root: rootRoute, folder: string) {
|
|
43
|
-
await Promise.all(readdirSync(`src/routes/${folder}`).map(async (item) => {
|
|
44
|
-
const stat = statSync(`src/routes/${folder}/${item}`);
|
|
45
|
-
if (stat.isFile()) {
|
|
46
|
-
const e = await import(`./${folder}/${item}`);
|
|
47
|
-
if (!e.route) throw new Error(`No route defined in ${item}`);
|
|
48
|
-
e.route(root, ZodAssert);
|
|
49
|
-
} else if (stat.isDirectory()) {
|
|
50
|
-
await importDir(root, `${folder}/${item}`);
|
|
51
|
-
}
|
|
52
|
-
}));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function importEsbuild(root: rootRoute) {
|
|
56
|
-
// "build": "tsc -b; esbuild main=src/main.tsx
|
|
57
|
-
// --outdir=public --bundle --target=es2020
|
|
58
|
-
// --platform=browser --jsx=automatic"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
root.defineRoute({
|
|
62
|
-
method: ['GET'],
|
|
63
|
-
path: /^\/(.*)/,
|
|
64
|
-
pathParams: ['reqpath'],
|
|
65
|
-
bodyFormat: "stream",
|
|
66
|
-
}, async state => {
|
|
67
|
-
await state.sendDevServer();
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
class ProxyPromise {
|
|
73
|
-
static getErrorStack(t: ProxyPromise) { return t.#error.stack; }
|
|
74
|
-
action: any;
|
|
75
|
-
table: any;
|
|
76
|
-
arg: any;
|
|
77
|
-
#error = new Error("An error occured for this request");
|
|
78
|
-
constructor({ action, table, arg }: Omit<ProxyPromise, "#error">) {
|
|
79
|
-
this.action = action;
|
|
80
|
-
this.table = table;
|
|
81
|
-
this.arg = arg;
|
|
82
|
-
Object.defineProperty(this, "toJSON", {
|
|
83
|
-
configurable: true,
|
|
84
|
-
enumerable: true,
|
|
85
|
-
value: () => ({
|
|
86
|
-
action: this.action,
|
|
87
|
-
table: this.table,
|
|
88
|
-
arg: this.arg,
|
|
89
|
-
dbnulls: this.arg.data && findValueInObject(this.arg.data, e => e === Prisma.DbNull),
|
|
90
|
-
jsnulls: this.arg.data && findValueInObject(this.arg.data, e => e === Prisma.JsonNull),
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function findValueInObject(val: any, predicate: (val: any) => boolean, keys: string[] = [], paths: string[] = []): any {
|
|
97
|
-
if (predicate(val)) {
|
|
98
|
-
paths.push(keys.join("/"));
|
|
99
|
-
return paths;
|
|
100
|
-
}
|
|
101
|
-
if (val && typeof val === "object") {
|
|
102
|
-
for (const key of Object.keys(val)) {
|
|
103
|
-
if (!val.hasOwnProperty(key)) continue;
|
|
104
|
-
findValueInObject(val[key], predicate, [...keys, key], paths);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
console.log(paths, keys);
|
|
108
|
-
return paths;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function capitalize<T extends string>(table: T): Capitalize<T> {
|
|
112
|
-
return table.slice(0, 1).toUpperCase() + table.slice(1) as any;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const argproxy: {
|
|
117
|
-
[Table in keyof PrismaClient]: {
|
|
118
|
-
[Action in keyof PrismaClient[Table]]:
|
|
119
|
-
PrismaClient[Table][Action] extends (...args: any) => any
|
|
120
|
-
? <T extends Parameters<PrismaClient[Table][Action]>[0]>(arg: T) => {
|
|
121
|
-
table: Table,
|
|
122
|
-
action: Action,
|
|
123
|
-
arg: T,
|
|
124
|
-
result: Awaited<ReturnType<PrismaClient[Table][Action]>>,
|
|
125
|
-
}
|
|
126
|
-
: never
|
|
127
|
-
}
|
|
128
|
-
} = (() => new Proxy<any>({}, {
|
|
129
|
-
get(target: any, table: any) {
|
|
130
|
-
return target[table] = target[table] || new Proxy<any>({}, {
|
|
131
|
-
get(target: any, action: any) {
|
|
132
|
-
return target[action] = target[action] || ((arg: any) => {
|
|
133
|
-
return new ProxyPromise({ action, table: capitalize(table) as any, arg });
|
|
134
|
-
})
|
|
135
|
-
},
|
|
136
|
-
})
|
|
137
|
-
},
|
|
138
|
-
}))();
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { RecipeKeyMap, RecipeManager } from "../managers/manager-recipes";
|
|
3
|
-
import { UserKeyMap, UserManager } from "../managers/manager-users";
|
|
4
|
-
import { SiteConfig } from "../router";
|
|
5
|
-
|
|
6
|
-
export { UserManager, UserManagerMap } from "./manager-users";
|
|
7
|
-
export { RecipeManager, RecipeManagerMap } from "./manager-recipes";
|
|
8
|
-
|
|
9
|
-
function isKeyOf<T extends Record<string, any>>(obj: T, key: string | number | symbol): key is keyof T {
|
|
10
|
-
return key in obj;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export const ManagerRoutes = (root: rootRoute, config: SiteConfig) => {
|
|
15
|
-
RecipeManager.defineRoutes(root, config);
|
|
16
|
-
UserManager.defineRoutes(root, config);
|
|
17
|
-
};
|