|
1 | 1 | import type { CreationOptional, InferAttributes, InferCreationAttributes, ModelStatic, NonAttribute, Sequelize } from 'sequelize'; |
2 | | -import { DataTypes, Model, Op } from 'sequelize'; |
| 2 | +import { DataTypes, Model, Op, QueryTypes } from 'sequelize'; |
3 | 3 | import type Orm from '@repository/storage/postgres/orm/sequelize/index.js'; |
4 | | -import type { Note, NoteCreationAttributes, NoteInternalId, NotePublicId } from '@domain/entities/note.js'; |
| 4 | +import type { Note, NoteContent, NoteCreationAttributes, NoteInternalId, NotePublicId } from '@domain/entities/note.js'; |
5 | 5 | import { UserModel } from '@repository/storage/postgres/orm/sequelize/user.js'; |
6 | 6 | import type { NoteSettingsModel } from './noteSettings.js'; |
7 | 7 | import type { NoteVisitsModel } from './noteVisits.js'; |
8 | 8 | import type { NoteHistoryModel } from './noteHistory.js'; |
| 9 | +import type { NoteTree } from '@domain/entities/noteTree.js'; |
9 | 10 |
|
10 | 11 | /* eslint-disable @typescript-eslint/naming-convention */ |
11 | 12 |
|
@@ -346,4 +347,86 @@ export default class NoteSequelizeStorage { |
346 | 347 |
|
347 | 348 | return notes; |
348 | 349 | } |
| 350 | + |
| 351 | + /** |
| 352 | + * Creates a tree of notes |
| 353 | + * @param noteId - public note id |
| 354 | + * @returns NoteTree |
| 355 | + */ |
| 356 | + public async getNoteTreebyNoteId(noteId: NoteInternalId): Promise<NoteTree | null> { |
| 357 | + // Fetch all notes and relations in a recursive query |
| 358 | + const query = ` |
| 359 | + WITH RECURSIVE note_tree AS ( |
| 360 | + SELECT |
| 361 | + n.id AS noteId, |
| 362 | + n.content, |
| 363 | + n.public_id, |
| 364 | + nr.parent_id |
| 365 | + FROM ${String(this.database.literal(this.tableName).val)} n |
| 366 | + LEFT JOIN ${String(this.database.literal('note_relations').val)} nr ON n.id = nr.note_id |
| 367 | + WHERE n.id = :startNoteId |
| 368 | + |
| 369 | + UNION ALL |
| 370 | +
|
| 371 | + SELECT |
| 372 | + n.id AS noteId, |
| 373 | + n.content, |
| 374 | + n.public_id, |
| 375 | + nr.parent_id |
| 376 | + FROM ${String(this.database.literal(this.tableName).val)} n |
| 377 | + INNER JOIN ${String(this.database.literal('note_relations').val)} nr ON n.id = nr.note_id |
| 378 | + INNER JOIN note_tree nt ON nr.parent_id = nt.noteId |
| 379 | + ) |
| 380 | + SELECT * FROM note_tree; |
| 381 | + `; |
| 382 | + |
| 383 | + const result = await this.model.sequelize?.query(query, { |
| 384 | + replacements: { startNoteId: noteId }, |
| 385 | + type: QueryTypes.SELECT, |
| 386 | + }); |
| 387 | + |
| 388 | + if (!result || result.length === 0) { |
| 389 | + return null; // No data found |
| 390 | + } |
| 391 | + |
| 392 | + type NoteRow = { |
| 393 | + noteid: NoteInternalId; |
| 394 | + public_id: NotePublicId; |
| 395 | + content: NoteContent; |
| 396 | + parent_id: NoteInternalId | null; |
| 397 | + }; |
| 398 | + |
| 399 | + const notes = result as NoteRow[]; |
| 400 | + |
| 401 | + const notesMap = new Map<NoteInternalId, NoteTree>(); |
| 402 | + const publicIdMap = new Map<NoteInternalId, NotePublicId>(); // Internal to Public ID lookup |
| 403 | + |
| 404 | + let root: NoteTree | null = null; |
| 405 | + |
| 406 | + // Step 1: Parse and initialize all notes |
| 407 | + notes.forEach((note) => { |
| 408 | + notesMap.set(note.noteid, { |
| 409 | + id: note.public_id, |
| 410 | + content: note.content, |
| 411 | + childNotes: [], |
| 412 | + }); |
| 413 | + |
| 414 | + publicIdMap.set(note.noteid, note.public_id); |
| 415 | + }); |
| 416 | + |
| 417 | + // Step 2: Build hierarchy |
| 418 | + notes.forEach((note) => { |
| 419 | + if (note.parent_id === null) { |
| 420 | + root = notesMap.get(note.noteid) ?? null; |
| 421 | + } else { |
| 422 | + const parent = notesMap.get(note.parent_id); |
| 423 | + |
| 424 | + if (parent) { |
| 425 | + parent.childNotes?.push(notesMap.get(note.noteid)!); |
| 426 | + } |
| 427 | + } |
| 428 | + }); |
| 429 | + |
| 430 | + return root; |
| 431 | + } |
349 | 432 | } |
0 commit comments