@tiddlywiki/mws 0.0.12 → 0.0.13

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.
Files changed (65) hide show
  1. package/README.md +3 -1
  2. package/dist/server.d.ts +923 -0
  3. package/dist/server.js +1763 -1415
  4. package/dist/server.js.map +1 -1
  5. package/editions/mws-docs/tiddlers/$__StoryList.tid +3 -3
  6. package/editions/mws-docs/tiddlers/MWS and SQLite.md +11 -0
  7. package/editions/mws-docs/tiddlers/Request Server.md +20 -0
  8. package/editions/mws-docs/tiddlers/Request Server.md.meta +5 -0
  9. package/package.json +16 -10
  10. package/prisma/schema.prisma +1 -0
  11. package/react-user-mgmt/package-lock.json +76 -321
  12. package/react-user-mgmt/package.json +2 -5
  13. package/react-user-mgmt/public/chunk-YALWN5OT.js +2 -0
  14. package/react-user-mgmt/public/chunk-YALWN5OT.js.map +7 -0
  15. package/react-user-mgmt/public/esm-QKDFEP3B.js +3 -0
  16. package/react-user-mgmt/public/esm-QKDFEP3B.js.map +7 -0
  17. package/react-user-mgmt/public/index.html +1 -26
  18. package/react-user-mgmt/public/main.js +68 -109
  19. package/react-user-mgmt/public/main.js.map +4 -4
  20. package/react-user-mgmt/public/stats.css +597 -0
  21. package/react-user-mgmt/public/stats.html +59 -0
  22. package/react-user-mgmt/public/stats.js +2013 -0
  23. package/react-user-mgmt/public/stats.json +1 -0
  24. package/react-user-mgmt/public/stats.svg +4 -0
  25. package/react-user-mgmt/tsconfig.json +3 -0
  26. package/dist/server.cjs +0 -16677
  27. package/dist/server.cjs.map +0 -1
  28. package/src/StateObject.ts +0 -316
  29. package/src/commander.ts +0 -417
  30. package/src/commands/divider.ts +0 -20
  31. package/src/commands/index.ts +0 -24
  32. package/src/commands/mws-init-store.ts +0 -77
  33. package/src/commands/mws-listen.ts +0 -136
  34. package/src/commands/mws-load-archive.ts +0 -281
  35. package/src/commands/mws-load-plugin-bags.ts +0 -151
  36. package/src/commands/mws-load-tiddlers.ts +0 -37
  37. package/src/commands/mws-load-wiki-folder.ts +0 -124
  38. package/src/commands/mws-render-tiddlywiki5.ts +0 -55
  39. package/src/commands/mws-save-archive.ts +0 -111
  40. package/src/commands/mws-save-tiddler-text.ts +0 -33
  41. package/src/commands/tests-complete.ts +0 -21
  42. package/src/global.ts +0 -247
  43. package/src/routes/TiddlerStore.ts +0 -722
  44. package/src/routes/bag-file-server.ts +0 -340
  45. package/src/routes/cache.ts +0 -25
  46. package/src/routes/index.ts +0 -138
  47. package/src/routes/managers/index.ts +0 -17
  48. package/src/routes/managers/manager-recipes.ts +0 -438
  49. package/src/routes/managers/manager-users.ts +0 -201
  50. package/src/routes/managers/router-tiddlers.ts +0 -313
  51. package/src/routes/router.ts +0 -429
  52. package/src/routes/services/PasswordService.ts +0 -178
  53. package/src/routes/services/attachments.ts +0 -259
  54. package/src/routes/services/sessions.ts +0 -166
  55. package/src/routes/tw-test.ts +0 -65
  56. package/src/server.ts +0 -151
  57. package/src/setupDevServer.ts +0 -71
  58. package/src/streamer.ts +0 -430
  59. package/src/tiddlywiki.ts +0 -94
  60. package/src/utils/data-checks.ts +0 -172
  61. package/src/utils/index.ts +0 -4
  62. package/src/utils/rootRoute.ts +0 -237
  63. package/src/utils/utils.ts +0 -403
  64. package/src/utils/zodAssert.ts +0 -269
  65. /package/prisma/{migrations/notes.md → notes.md} +0 -0
@@ -1,722 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { SiteConfig } from "./router";
4
- import { AttachmentService, TiddlerFields } from "./services/attachments";
5
- import { ok } from "assert";
6
- import { Commander } from "../commander";
7
- import { FileInfoTiddlers } from "tiddlywiki";
8
- import { UserError } from "../utils";
9
-
10
- /**
11
-
12
- ### NOTES from attachments file
13
-
14
- Class to handle the attachments in the filing system
15
-
16
- The store folder looks like this:
17
-
18
- store/
19
- inbox/ - files that are in the process of being uploaded via a multipart form upload
20
- 202402282125432742/
21
- 0
22
- 1
23
- ...
24
- ...
25
- files/ - files that are the text content of large tiddlers
26
- b7def178-79c4-4d88-b7a4-39763014a58b/
27
- data.jpg - the extension is provided for convenience when directly inspecting the file system
28
- meta.json - contains:
29
- {
30
- "filename": "data.jpg",
31
- "type": "video/mp4",
32
- "uploaded": "2024021821224823"
33
- }
34
- database.sql - The database file (managed by Prisma)
35
-
36
- */
37
- export class TiddlerStore {
38
- attachService: AttachmentService;
39
- storePath: string;
40
- config;
41
- fieldModules;
42
- constructor(
43
- private commander: Commander,
44
- public prisma: PrismaTxnClient
45
- ) {
46
- this.fieldModules = this.commander.$tw.Tiddler.fieldModules;
47
- this.attachService = new commander.AttachmentService(commander.siteConfig, prisma);
48
- this.storePath = commander.siteConfig.storePath;
49
- this.config = commander.siteConfig;
50
- }
51
-
52
- /*
53
- Returns null if a bag/recipe name is valid, or a string error message if not
54
- */
55
- validateItemName(name: string, allowPrivilegedCharacters: boolean) {
56
- if (typeof name !== "string") {
57
- return "Not a valid string";
58
- }
59
- if (name.length > 256) {
60
- return "Too long";
61
- }
62
- // Removed ~ from this list temporarily
63
- if (allowPrivilegedCharacters) {
64
- if (!(/^[^\s\u00A0\x00-\x1F\x7F`!@#%^&*()+={}\[\];\'\"<>,\\\?]+$/g.test(name))) {
65
- return "Invalid character(s)";
66
- }
67
- } else {
68
- if (!(/^[^\s\u00A0\x00-\x1F\x7F`!@#$%^&*()+={}\[\];:\'\"<>.,\/\\\?]+$/g.test(name))) {
69
- return "Invalid character(s)";
70
- }
71
- }
72
- return null;
73
- }
74
- /*
75
- Returns null if the argument is an array of valid bag/recipe names, or a string error message if not
76
- */
77
- validateItemNames(names: string[], allowPrivilegedCharacters: boolean) {
78
- if (!Array.isArray(names)) {
79
- return "Not a valid array";
80
- }
81
- var errors = [];
82
- for (const name of names) {
83
- const result = this.validateItemName(name, allowPrivilegedCharacters);
84
- if (result && errors.indexOf(result) === -1) {
85
- errors.push(result);
86
- }
87
- }
88
- if (errors.length === 0) {
89
- return null;
90
- } else {
91
- return errors.join("\n");
92
- }
93
- }
94
- async upsertRecipe(
95
- recipe_name: PrismaField<"Recipes", "recipe_name">,
96
- description: PrismaField<"Recipes", "description">,
97
- bags: { bag_name: PrismaField<"Bags", "bag_name">, with_acl: PrismaField<"Recipe_bags", "with_acl"> }[],
98
- { allowPrivilegedCharacters = false }: { allowPrivilegedCharacters?: boolean; } = {}
99
- ) {
100
- const [recipe_, recipe_bags_] = this.upsertRecipe_PrismaArray(recipe_name, description, bags, { allowPrivilegedCharacters });
101
- const recipe = await recipe_;
102
- await recipe_bags_;
103
- return recipe;
104
- }
105
- upsertRecipe_PrismaArray(
106
- recipe_name: PrismaField<"Recipes", "recipe_name">,
107
- description: PrismaField<"Recipes", "description">,
108
- bags: { bag_name: PrismaField<"Bags", "bag_name">, with_acl: PrismaField<"Recipe_bags", "with_acl"> }[],
109
- { allowPrivilegedCharacters = false }: { allowPrivilegedCharacters?: boolean; } = {}
110
- ) {
111
-
112
- const validationRecipeName = this.validateItemName(recipe_name, allowPrivilegedCharacters);
113
- if (validationRecipeName) throw validationRecipeName;
114
- if (bags.length === 0) throw new UserError("Recipes must contain at least one bag");
115
-
116
- return [
117
- this.prisma.recipes.upsert({
118
- where: { recipe_name },
119
- update: { description, recipe_bags: { deleteMany: {} } },
120
- create: { recipe_name, description },
121
- select: { recipe_id: true }
122
- }),
123
- this.prisma.recipes.update({
124
- where: { recipe_name },
125
- data: {
126
- recipe_bags: {
127
- create: bags.map(({ bag_name, with_acl }, position) => ({
128
- bag: { connect: { bag_name } },
129
- position,
130
- with_acl
131
- }))
132
- }
133
- }
134
- })
135
- ];
136
-
137
- }
138
- async upsertBag(
139
- bag_name: PrismaField<"Bags", "bag_name">,
140
- description: PrismaField<"Bags", "description">,
141
- is_plugin: PrismaField<"Bags", "is_plugin">,
142
- { allowPrivilegedCharacters = false }: { allowPrivilegedCharacters?: boolean; } = {}
143
- ) {
144
- return await this.upsertBag_PrismaPromise(bag_name, description, is_plugin, {
145
- allowPrivilegedCharacters
146
- });
147
- }
148
- upsertBag_PrismaPromise(
149
- bag_name: PrismaField<"Bags", "bag_name">,
150
- description: PrismaField<"Bags", "description">,
151
- is_plugin: PrismaField<"Bags", "is_plugin">,
152
- { allowPrivilegedCharacters = false }: { allowPrivilegedCharacters?: boolean; } = {}
153
- ) {
154
-
155
- const validationBagName = this.validateItemName(bag_name, allowPrivilegedCharacters);
156
- if (validationBagName) throw validationBagName;
157
-
158
- return this.prisma.bags.upsert({
159
- where: { bag_name },
160
- update: { description, is_plugin },
161
- create: { bag_name, description, is_plugin },
162
- select: { bag_id: true }
163
- });
164
-
165
- }
166
- /**
167
- * You probably want to call this inside a transaction.
168
- *
169
- ```js
170
- const tiddlersFromPath = $tw.loadTiddlersFromPath(tiddlerPath);
171
- ```
172
- */
173
- async saveTiddlersFromPath(
174
- tiddlersFromPath: { tiddlers: TiddlerFields[] }[],
175
- bag_name: PrismaField<"Bags", "bag_name">
176
- ) {
177
-
178
- }
179
- saveTiddlersFromPath_PrismaArray(
180
- tiddlersFromPath: { tiddlers: TiddlerFields[] }[],
181
- bag_name: PrismaField<"Bags", "bag_name">
182
- ) {
183
- return [
184
- this.prisma.tiddlers.deleteMany({
185
- where: { bag: { bag_name } }
186
- }),
187
- ...tiddlersFromPath.flatMap(({ tiddlers }) =>
188
- tiddlers.flatMap(tiddler =>
189
- this.saveBagTiddlerFields_PrismaArray(tiddler, bag_name, null)
190
- )
191
- )
192
- ]
193
-
194
- }
195
-
196
-
197
- /*
198
- Returns {tiddler_id:}
199
- */
200
- async saveBagTiddler(
201
- incomingTiddlerFields: TiddlerFields,
202
- bag_name: PrismaField<"Bags", "bag_name">
203
- ) {
204
- const { title } = incomingTiddlerFields;
205
-
206
- const existing_attachment_hash = await this.prisma.tiddlers.findFirst({
207
- where: { title, bag: { bag_name } },
208
- select: { attachment_hash: true }
209
- }).then(e => e?.attachment_hash ?? null);
210
-
211
- const { tiddlerFields, attachment_hash } = await this.attachService.processIncomingTiddler({
212
- tiddlerFields: incomingTiddlerFields,
213
- existing_attachment_hash,
214
- existing_canonical_uri: existing_attachment_hash && this.attachService.makeCanonicalUri(bag_name, incomingTiddlerFields.title)
215
- });
216
- return this.saveBagTiddlerFields(tiddlerFields, bag_name, attachment_hash);
217
-
218
- }
219
- /*
220
- Create a tiddler in a bag adopting the specified file as the attachment. The attachment file must be on the same disk as the attachment store
221
- Options include:
222
-
223
- filepath - filepath to the attachment file
224
- hash - string hash of the attachment file
225
- type - content type of file as uploaded
226
-
227
- Returns {tiddler_id:}
228
- */
229
- async saveBagTiddlerWithAttachment(
230
- incomingTiddlerFields: TiddlerFields,
231
- bag_name: PrismaField<"Bags", "bag_name">,
232
- options: {
233
- filepath: string;
234
- hash: string;
235
- type: string;
236
- _canonical_uri: string;
237
- }
238
- ) {
239
- const attachment_hash = await this.attachService.adoptAttachment({
240
- // options.filepath, options.type, options.hash, options._canonical_uri
241
- incomingFilepath: options.filepath,
242
- type: options.type,
243
- hash: options.hash,
244
- _canonical_uri: options._canonical_uri
245
- });
246
- if (attachment_hash) {
247
- return this.saveBagTiddlerFields(incomingTiddlerFields, bag_name, attachment_hash);
248
- } else {
249
- return null;
250
- }
251
- }
252
- /*
253
- Returns {tiddler_id:,bag_name:}
254
-
255
- The critical difference here is that the tiddler gets saved to the top bag.
256
- */
257
- async saveRecipeTiddler(
258
- incomingTiddlerFields: TiddlerFields,
259
- recipe_name: PrismaField<"Recipes", "recipe_name">
260
- ) {
261
- const { title } = incomingTiddlerFields;
262
- const currentBag = await this.getRecipeBagWithTiddler({ recipe_name, title });
263
- const existing_attachment_hash = currentBag && await this.prisma.tiddlers.findFirst({
264
- where: { title, bag: { bag_name: currentBag.bag.bag_name } },
265
- select: { attachment_hash: true }
266
- }).then(e => e?.attachment_hash ?? null);
267
-
268
- const { tiddlerFields, attachment_hash } = await this.attachService.processIncomingTiddler({
269
- existing_attachment_hash,
270
- existing_canonical_uri: incomingTiddlerFields._canonical_uri,
271
- tiddlerFields: incomingTiddlerFields
272
- });
273
- return this.saveRecipeTiddlerFields(tiddlerFields, recipe_name, attachment_hash);
274
- }
275
-
276
- /*
277
- returns {tiddler_id:,tiddler:}
278
- */
279
- async getBagTiddler(options: {
280
- title: PrismaField<"Tiddlers", "title">;
281
- bag_name?: PrismaField<"Bags", "bag_name">;
282
- bag_id?: PrismaField<"Bags", "bag_id">;
283
- }) {
284
- const { title, bag_name, bag_id } = options;
285
- ok(bag_name || bag_id, "bag_name or bag_id must be provided");
286
-
287
- const tiddler = await this.prisma.tiddlers.findFirst({
288
- where: bag_name
289
- ? { title, bag: { bag_name }, is_deleted: false }
290
- : { title, bag_id, is_deleted: false },
291
- select: { tiddler_id: true }
292
- });
293
- if (!tiddler) return null;
294
-
295
- var tiddlerInfo = await this.getTiddlerInfo(tiddler.tiddler_id);
296
- if (!tiddlerInfo) return null;
297
-
298
- // if (tiddlerInfo.is_plugin) {
299
- // const pluginBag = await this.prisma.bags.findUnique({
300
- // where: { bag_name: tiddlerInfo.bag_name },
301
- // include: { tiddlers: { include: { fields: true } } }
302
- // });
303
- // }
304
-
305
- return {
306
- ...tiddlerInfo,
307
- tiddler: this.attachService.processOutgoingTiddler(tiddlerInfo)
308
- };
309
-
310
- }
311
-
312
- async getRecipeTiddler(
313
- title: PrismaField<"Tiddlers", "title">,
314
- recipe_name: PrismaField<"Recipes", "recipe_name">
315
- ) {
316
-
317
- const recipe_bag = await this.getRecipeBagWithTiddler({ recipe_name, title });
318
-
319
- if (!recipe_bag) return null;
320
-
321
- return await this.getBagTiddler({ title, bag_id: recipe_bag.bag_id });
322
-
323
- }
324
-
325
-
326
- async getRecipeBagWithTiddler({ recipe_name, title }: { recipe_name: string; title: string; }) {
327
-
328
- return await this.prisma.recipe_bags.findFirst({
329
- include: { bag: true, recipe: true },
330
- where: {
331
- recipe: { recipe_name },
332
- bag: { tiddlers: { some: { title, is_deleted: false } } }
333
- },
334
- orderBy: { position: "asc" }
335
- });
336
- }
337
-
338
- async getRecipeWritableBag(
339
- recipe_name: PrismaField<"Recipes", "recipe_name">
340
- ) {
341
- const recipe_bag = await this.prisma.recipe_bags.findFirst({
342
- select: { bag: true },
343
- where: { recipe: { recipe_name }, position: 0 }
344
- });
345
- if (!recipe_bag) return null;
346
- return recipe_bag.bag;
347
- }
348
-
349
-
350
- async getTiddlerInfo(
351
- tiddler_id: PrismaField<"Tiddlers", "tiddler_id">
352
- ) {
353
-
354
- const tiddler = await this.prisma.tiddlers.findUnique({
355
- where: { tiddler_id, is_deleted: false },
356
- include: { fields: true, bag: true }
357
- });
358
-
359
- if (!tiddler) return null;
360
-
361
- if (tiddler.bag.is_plugin && tiddler.title as string !== tiddler.bag.bag_name as string)
362
- throw new UserError("Can only get tiddler info for a plugin bag itself");
363
-
364
- return {
365
- bag_name: tiddler.bag.bag_name,
366
- is_plugin_bag: tiddler.bag.is_plugin,
367
- tiddler_id: tiddler.tiddler_id,
368
- attachment_hash: tiddler.attachment_hash,
369
- tiddler: Object.fromEntries([
370
- ...tiddler.fields.map(e => [e.field_name, e.field_value] as const),
371
- ["title", tiddler.title]
372
- ]) as TiddlerFields
373
- };
374
- }
375
- /*
376
- Get an attachment ready to stream. Returns null if there is an error or:
377
- tiddler_id: revision of tiddler
378
- stream: stream of file
379
- type: type of file
380
- Returns {tiddler_id:,bag_name:}
381
- */
382
- async getBagTiddlerStream(
383
- title: PrismaField<"Tiddlers", "title">,
384
- bag_name: PrismaField<"Bags", "bag_name">
385
- ) {
386
- const tiddlerInfo = await this.getBagTiddler({ title, bag_name });
387
- if (!tiddlerInfo) return null;
388
-
389
- if (tiddlerInfo.attachment_hash) {
390
- return {
391
- ...await this.attachService.getAttachmentStream(tiddlerInfo.attachment_hash) ?? {},
392
- tiddler_id: tiddlerInfo.tiddler_id,
393
- bag_name: bag_name
394
- };
395
- } else {
396
- const { Readable } = require('stream');
397
- const stream = new Readable();
398
- stream._read = () => {
399
- // Push data
400
- const type = tiddlerInfo.tiddler.type || "text/plain";
401
- stream.push(tiddlerInfo.tiddler.text || "", (this.config.contentTypeInfo[type] || { encoding: "utf8" }).encoding);
402
- // Push null to indicate the end of the stream
403
- stream.push(null);
404
- };
405
- return {
406
- tiddler_id: tiddlerInfo.tiddler_id,
407
- bag_name: bag_name,
408
- stream: stream,
409
- type: tiddlerInfo.tiddler.type || "text/plain"
410
- };
411
- }
412
-
413
- }
414
-
415
- /**
416
- Returns {tiddler_id:,bag_name:} or null if the recipe is empty
417
- */
418
- async saveRecipeTiddlerFields(
419
- tiddlerFields: TiddlerFields,
420
- recipe_name: PrismaField<"Recipes", "recipe_name">,
421
- attachment_hash: PrismaField<"Tiddlers", "attachment_hash">
422
- ) {
423
-
424
- const recipe = await this.prisma.recipes.findUnique({
425
- where: { recipe_name },
426
- include: {
427
- recipe_bags: {
428
- // we're saving to the top most bag
429
- where: { position: 0 },
430
- select: { bag: { select: { bag_id: true, bag_name: true } } },
431
- }
432
- }
433
- });
434
-
435
- if (!recipe) throw new UserError("Recipe not found");
436
-
437
- const bag_name = recipe.recipe_bags[0]?.bag.bag_name;
438
-
439
- if (!bag_name) throw new UserError("Recipe has no bag at position 0");
440
-
441
- // Save the tiddler to the specified bag
442
- var { tiddler_id } = await this.saveBagTiddlerFields(tiddlerFields, bag_name, attachment_hash);
443
-
444
- return { tiddler_id, bag_name };
445
- // // Find the topmost bag in the recipe
446
- // var row = this.engine.runStatementGet(`
447
- // SELECT b.bag_name
448
- // FROM bags AS b
449
- // JOIN (
450
- // SELECT rb.bag_id
451
- // FROM recipe_bags AS rb
452
- // WHERE rb.recipe_id = (
453
- // SELECT recipe_id
454
- // FROM recipes
455
- // WHERE recipe_name = $recipe_name
456
- // )
457
- // ORDER BY rb.position DESC
458
- // LIMIT 1
459
- // ) AS selected_bag
460
- // ON b.bag_id = selected_bag.bag_id
461
- // `, {
462
- // $recipe_name: recipe_name
463
- // });
464
- // if (!row) {
465
- // return null;
466
- // }
467
- // // Save the tiddler to the topmost bag
468
- // var info = this.saveBagTiddler(tiddlerFields, row.bag_name, attachment_hash);
469
- // return {
470
- // tiddler_id: info.tiddler_id,
471
- // bag_name: row.bag_name
472
- // };
473
- }
474
-
475
- async saveBagTiddlerFields(
476
- tiddlerFields: TiddlerFields,
477
- bag_name: PrismaField<"Bags", "bag_name">,
478
- attachment_hash: PrismaField<"Tiddlers", "attachment_hash">
479
- ) {
480
- const [deletion, creation] = this.saveBagTiddlerFields_PrismaArray(tiddlerFields, bag_name, attachment_hash);
481
- await deletion;
482
- return await creation;
483
- }
484
- saveBagTiddlerFields_PrismaArray(
485
- tiddlerFields: TiddlerFields,
486
- bag_name: PrismaField<"Bags", "bag_name">,
487
- attachment_hash: PrismaField<"Tiddlers", "attachment_hash">
488
- ) {
489
-
490
- const { title } = tiddlerFields;
491
- if (!title) {
492
- console.error(tiddlerFields);
493
- throw new Error("Tiddler must have a title");
494
- }
495
-
496
- const deletion = this.prisma.tiddlers.deleteMany({
497
- where: { bag: { bag_name }, title }
498
- });
499
-
500
- const encodeTiddlerFields = ([field_name, field_value]: [string, any]) => {
501
- const fieldModule = this.fieldModules[field_name];
502
- if (typeof field_value === "string") return [field_name, field_value];
503
- if (fieldModule && fieldModule.stringify) field_value = fieldModule.stringify(field_value);
504
- if (typeof field_value === "number") field_value = field_value.toString();
505
- if (typeof field_value === "boolean") field_value = field_value ? "true" : "false";
506
- if (typeof field_value === "undefined" || field_value === null) field_value = "";
507
- return [field_name, field_value];
508
- };
509
-
510
- const creation = this.prisma.tiddlers.create({
511
- data: {
512
- bag: { connect: { bag_name } },
513
- title,
514
- is_deleted: false,
515
- attachment_hash: attachment_hash || null,
516
- fields: {
517
- create: Object.entries(tiddlerFields)
518
- .map(encodeTiddlerFields)
519
- .map(([field_name, field_value]) => ({ field_name, field_value }))
520
- }
521
- },
522
- select: {
523
- tiddler_id: true
524
- }
525
- });
526
-
527
- return [deletion, creation] as const;
528
-
529
- // attachment_hash = attachment_hash || null;
530
- // // Update the tiddlers table
531
- // var info = this.engine.runStatement(`
532
- // INSERT OR REPLACE INTO tiddlers (bag_id, title, is_deleted, attachment_hash)
533
- // VALUES (
534
- // (SELECT bag_id FROM bags WHERE bag_name = $bag_name),
535
- // $title,
536
- // FALSE,
537
- // $attachment_hash
538
- // )
539
- // `, {
540
- // $title: tiddlerFields.title,
541
- // $attachment_hash: attachment_hash,
542
- // $bag_name: bag_name
543
- // });
544
- // // Update the fields table
545
- // this.engine.runStatement(`
546
- // INSERT OR REPLACE INTO fields (tiddler_id, field_name, field_value)
547
- // SELECT
548
- // t.tiddler_id,
549
- // json_each.key AS field_name,
550
- // json_each.value AS field_value
551
- // FROM (
552
- // SELECT tiddler_id
553
- // FROM tiddlers
554
- // WHERE bag_id = (
555
- // SELECT bag_id
556
- // FROM bags
557
- // WHERE bag_name = $bag_name
558
- // ) AND title = $title
559
- // ) AS t
560
- // JOIN json_each($field_values) AS json_each
561
- // `, {
562
- // $title: tiddlerFields.title,
563
- // $bag_name: bag_name,
564
- // $field_values: JSON.stringify(Object.assign({}, tiddlerFields, { title: undefined }))
565
- // });
566
- // return {
567
- // tiddler_id: info.lastInsertRowid
568
- // };
569
- }
570
- /*
571
- Returns {tiddler_id:,bag_name:}
572
- */
573
- async deleteRecipeTiddler(
574
- recipe_name: PrismaField<"Recipes", "recipe_name">,
575
- title: PrismaField<"Tiddlers", "title">
576
- ) {
577
-
578
- const currentBag = await this.getRecipeBagWithTiddler({ recipe_name, title });
579
- if (!currentBag)
580
- throw new UserError("Tiddler not found in recipe");
581
- if (currentBag.position !== 0)
582
- throw new UserError("Cannot delete tiddler from non-top bag");
583
- return await this.deleteBagTiddler(title, currentBag.bag.bag_name);
584
- }
585
- /**
586
- Returns {tiddler_id:} of the delete marker
587
- */
588
- async deleteBagTiddler(
589
- title: PrismaField<"Tiddlers", "title">,
590
- bag_name: PrismaField<"Bags", "bag_name">
591
- ) {
592
- await this.prisma.tiddlers.deleteMany({
593
- where: { title, bag: { bag_name } },
594
- });
595
- const { tiddler_id } = await this.prisma.tiddlers.create({
596
- data: {
597
- title,
598
- bag: { connect: { bag_name } },
599
- is_deleted: true,
600
- attachment_hash: null,
601
- },
602
- select: {
603
- tiddler_id: true
604
- }
605
- });
606
-
607
- return { bag_name, tiddler_id }
608
-
609
- // // Delete the fields of this tiddler
610
- // this.engine.runStatement(`
611
- // DELETE FROM fields
612
- // WHERE tiddler_id IN (
613
- // SELECT t.tiddler_id
614
- // FROM tiddlers AS t
615
- // INNER JOIN bags AS b ON t.bag_id = b.bag_id
616
- // WHERE b.bag_name = $bag_name AND t.title = $title
617
- // )
618
- // `, {
619
- // $title: title,
620
- // $bag_name: bag_name
621
- // });
622
- // // Mark the tiddler itself as deleted
623
- // const rowDeleteMarker = this.engine.runStatement(`
624
- // INSERT OR REPLACE INTO tiddlers (bag_id, title, is_deleted, attachment_hash)
625
- // VALUES (
626
- // (SELECT bag_id FROM bags WHERE bag_name = $bag_name),
627
- // $title,
628
- // TRUE,
629
- // NULL
630
- // )
631
- // `, {
632
- // $title: title,
633
- // $bag_name: bag_name
634
- // });
635
- // return { tiddler_id: rowDeleteMarker.lastInsertRowid };
636
- }
637
-
638
- async getRecipeTiddlersByBag(
639
- recipe_name: PrismaField<"Recipes", "recipe_name">,
640
- options: {
641
- // how do you limit a list of unique titles?
642
- // limit?: number,
643
- last_known_tiddler_id?: number,
644
- include_deleted?: boolean,
645
- } = {}
646
- ) {
647
- // In prisma it's easy to get the top bag for a specific title.
648
- // To get all titles we basically have to get all bags and manually find the top one.
649
- const lastid = options.last_known_tiddler_id;
650
- const withDeleted = options.include_deleted;
651
- const bags = await this.prisma.recipe_bags.findMany({
652
- where: { recipe: { recipe_name } },
653
- select: {
654
- position: true,
655
- bag: {
656
- select: {
657
- bag_id: true,
658
- bag_name: true,
659
- is_plugin: true,
660
- tiddlers: {
661
- select: {
662
- title: true,
663
- tiddler_id: true,
664
- is_deleted: true,
665
- },
666
- where: {
667
- is_deleted: withDeleted ? undefined : false,
668
- tiddler_id: lastid ? { gt: lastid } : undefined
669
- },
670
- }
671
- }
672
- }
673
- },
674
- });
675
-
676
-
677
- return bags.map(e => ({
678
- bag_id: e.bag.bag_id,
679
- bag_name: e.bag.bag_name,
680
- is_plugin: e.bag.is_plugin,
681
- position: e.position,
682
- tiddlers: e.bag.tiddlers
683
- }));
684
-
685
- // // Get the recipe ID
686
- // const rowsCheckRecipe = this.engine.runStatementGet(`
687
- // SELECT recipe_id FROM recipes WHERE recipes.recipe_name = $recipe_name
688
- // `, {
689
- // $recipe_name: recipe_name
690
- // });
691
- // if (!rowsCheckRecipe) {
692
- // return null;
693
- // }
694
- // const recipe_id = rowsCheckRecipe.recipe_id;
695
- // // Compose the query to get the tiddlers
696
- // const params = {
697
- // $recipe_id: recipe_id
698
- // };
699
- // if (options.limit) {
700
- // params.$limit = options.limit.toString();
701
- // }
702
- // if (options.last_known_tiddler_id) {
703
- // params.$last_known_tiddler_id = options.last_known_tiddler_id;
704
- // }
705
- // const rows = this.engine.runStatementGetAll(`
706
- // SELECT title, tiddler_id, is_deleted, bag_name
707
- // FROM (
708
- // SELECT t.title, t.tiddler_id, t.is_deleted, b.bag_name, MAX(rb.position) AS position
709
- // FROM bags AS b
710
- // INNER JOIN recipe_bags AS rb ON b.bag_id = rb.bag_id
711
- // INNER JOIN tiddlers AS t ON b.bag_id = t.bag_id
712
- // WHERE rb.recipe_id = $recipe_id
713
- // ${options.include_deleted ? "" : "AND t.is_deleted = FALSE"}
714
- // ${options.last_known_tiddler_id ? "AND tiddler_id > $last_known_tiddler_id" : ""}
715
- // GROUP BY t.title
716
- // ORDER BY t.title, tiddler_id DESC
717
- // ${options.limit ? "LIMIT $limit" : ""}
718
- // )
719
- // `, params);
720
- // return rows;
721
- }
722
- }