ocean-brain 0.3.4 → 0.4.1
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/index.js +5 -0
- package/package.json +6 -5
- package/scripts/postinstall-prisma.mjs +3 -2
- package/server/client/dist/assets/Calendar-D4KkvcU8.js +25 -0
- package/server/client/dist/assets/Callout-CaSzc-1y.js +1 -0
- package/server/client/dist/assets/Graph-C8pBdJzV.js +1 -0
- package/server/client/dist/assets/{Image-ZxF459sC.js → Image-DlJ4MDo3.js} +1 -1
- package/server/client/dist/assets/{Image.es-BQfyKc2-.js → Image.es-C8g5NwNt.js} +1 -1
- package/server/client/dist/assets/Note-ODOESft3.js +21 -0
- package/server/client/dist/assets/Note-W4f6H9K0.css +1 -0
- package/server/client/dist/assets/Plus.es-Bkl1ZXE0.js +1 -0
- package/server/client/dist/assets/Reminders-FrTR9ny8.js +1 -0
- package/server/client/dist/assets/Search-BPf2LO1Y.js +1 -0
- package/server/client/dist/assets/{SurfaceCard-nZNf9lyE.js → SurfaceCard-WSk7zWeM.js} +1 -1
- package/server/client/dist/assets/Tag-uArdLjo1.js +1 -0
- package/server/client/dist/assets/TagNotes-DDvXHB9I.js +1 -0
- package/server/client/dist/assets/{Trash.es-BGoN5q9L.js → Trash.es-DumPfiUz.js} +1 -1
- package/server/client/dist/assets/ViewNotes-BVu9lEs-.js +1 -0
- package/server/client/dist/assets/Views-D4LGh30P.js +1 -0
- package/server/client/dist/assets/{image.api-0mMkElZk.js → image.api-D3Ei3fbf.js} +1 -1
- package/server/client/dist/assets/index-46ZUXbW5.js +232 -0
- package/server/client/dist/assets/index-C7YGbIuA.js +1 -0
- package/server/client/dist/assets/{index-DL7pUwq2.css → index-D2gRB1ig.css} +1 -1
- package/server/client/dist/assets/manage-image-DiuREdeE.js +1 -0
- package/server/client/dist/assets/manage-image-detail-D8Eorq3u.js +1 -0
- package/server/client/dist/assets/mcp-CvCGaYSz.js +1 -0
- package/server/client/dist/assets/{note-core-BCgMq5QA.js → note-core-GgFcksAF.js} +1 -1
- package/server/client/dist/assets/{note-vendor-BofYbzmZ.js → note-vendor-B0t5F04o.js} +5 -5
- package/server/client/dist/assets/{placeholder-i-uNe71U.js → placeholder-znZDfNAE.js} +2 -2
- package/server/client/dist/assets/trash-R0sww0ur.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-tNMwwRnx.js → useReminderMutate-Bkj-oIm4.js} +2 -2
- package/server/client/dist/assets/view-dashboard-ClpF_qLM.js +90 -0
- package/server/client/dist/index.html +4 -4
- package/server/dist/app.js +8 -84
- package/server/dist/app.js.map +1 -1
- package/server/dist/features/auth/http/api.js +40 -0
- package/server/dist/features/auth/http/api.js.map +1 -0
- package/server/dist/features/auth/http/login-page.js +28 -0
- package/server/dist/features/auth/http/login-page.js.map +1 -0
- package/server/dist/features/auth/http/pages.js +50 -0
- package/server/dist/features/auth/http/pages.js.map +1 -0
- package/server/dist/features/auth/service.js +63 -0
- package/server/dist/features/auth/service.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.field.resolver.js +5 -0
- package/server/dist/features/cache/graphql/cache.field.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.mutation.resolver.js +49 -0
- package/server/dist/features/cache/graphql/cache.mutation.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.query.resolver.js +21 -0
- package/server/dist/features/cache/graphql/cache.query.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.resolver.js +12 -0
- package/server/dist/features/cache/graphql/cache.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.type-defs.js +32 -0
- package/server/dist/features/cache/graphql/cache.type-defs.js.map +1 -0
- package/server/dist/features/cache/graphql/index.js +21 -0
- package/server/dist/features/cache/graphql/index.js.map +1 -0
- package/server/dist/features/image/graphql/image.field.resolver.js +14 -0
- package/server/dist/features/image/graphql/image.field.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.mutation.resolver.js +21 -0
- package/server/dist/features/image/graphql/image.mutation.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.query.resolver.js +40 -0
- package/server/dist/features/image/graphql/image.query.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.resolver.js +12 -0
- package/server/dist/features/image/graphql/image.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.type-defs.js +31 -0
- package/server/dist/features/image/graphql/image.type-defs.js.map +1 -0
- package/server/dist/features/image/graphql/index.js +16 -0
- package/server/dist/features/image/graphql/index.js.map +1 -0
- package/server/dist/features/image/http/upload.js +48 -0
- package/server/dist/features/image/http/upload.js.map +1 -0
- package/server/dist/features/image/services/delete.js +52 -0
- package/server/dist/features/image/services/delete.js.map +1 -0
- package/server/dist/{modules/remote-image.js → features/image/services/remote-fetch.js} +1 -1
- package/server/dist/features/image/services/remote-fetch.js.map +1 -0
- package/server/dist/{modules/image-upload.js → features/image/services/upload.js} +4 -8
- package/server/dist/features/image/services/upload.js.map +1 -0
- package/server/dist/{views/mcp-admin.js → features/mcp-admin/http/handlers.js} +3 -3
- package/server/dist/features/mcp-admin/http/handlers.js.map +1 -0
- package/server/dist/{modules/mcp-admin.js → features/mcp-admin/service.js} +3 -3
- package/server/dist/features/mcp-admin/service.js.map +1 -0
- package/server/dist/features/note/graphql/index.js +8 -0
- package/server/dist/features/note/graphql/index.js.map +1 -0
- package/server/dist/features/note/graphql/note.field.resolver.js +12 -0
- package/server/dist/features/note/graphql/note.field.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.graphql.shared.js +23 -0
- package/server/dist/features/note/graphql/note.graphql.shared.js.map +1 -0
- package/server/dist/features/note/graphql/note.mutation.resolver.js +140 -0
- package/server/dist/features/note/graphql/note.mutation.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.query.resolver.js +385 -0
- package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.resolver.js +14 -0
- package/server/dist/features/note/graphql/note.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.type-defs.js +187 -0
- package/server/dist/features/note/graphql/note.type-defs.js.map +1 -0
- package/server/dist/{views/note.js → features/note/http/mcp.js} +27 -15
- package/server/dist/features/note/http/mcp.js.map +1 -0
- package/server/dist/{modules/note-authoring.js → features/note/services/authoring.js} +25 -7
- package/server/dist/features/note/services/authoring.js.map +1 -0
- package/server/dist/{modules/note-cleanup.js → features/note/services/cleanup.js} +14 -23
- package/server/dist/features/note/services/cleanup.js.map +1 -0
- package/server/dist/features/note/services/search-backfill.js +84 -0
- package/server/dist/features/note/services/search-backfill.js.map +1 -0
- package/server/dist/features/note/services/search.js +197 -0
- package/server/dist/features/note/services/search.js.map +1 -0
- package/server/dist/{modules/note-snapshot.js → features/note/services/snapshot.js} +51 -49
- package/server/dist/features/note/services/snapshot.js.map +1 -0
- package/server/dist/{modules/note-tag-filter.js → features/note/services/tag-filter.js} +2 -6
- package/server/dist/features/note/services/tag-filter.js.map +1 -0
- package/server/dist/{modules/note-trash.js → features/note/services/trash.js} +64 -34
- package/server/dist/features/note/services/trash.js.map +1 -0
- package/server/dist/features/placeholder/graphql/index.js +27 -0
- package/server/dist/features/placeholder/graphql/index.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js +5 -0
- package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js +72 -0
- package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js +36 -0
- package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.resolver.js +12 -0
- package/server/dist/features/placeholder/graphql/placeholder.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.type-defs.js +50 -0
- package/server/dist/features/placeholder/graphql/placeholder.type-defs.js.map +1 -0
- package/server/dist/features/reminder/graphql/index.js +27 -0
- package/server/dist/features/reminder/graphql/index.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.field.resolver.js +10 -0
- package/server/dist/features/reminder/graphql/reminder.field.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js +59 -0
- package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.query.resolver.js +79 -0
- package/server/dist/features/reminder/graphql/reminder.query.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.resolver.js +12 -0
- package/server/dist/features/reminder/graphql/reminder.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.type-defs.js +51 -0
- package/server/dist/features/reminder/graphql/reminder.type-defs.js.map +1 -0
- package/server/dist/features/tag/graphql/index.js +14 -0
- package/server/dist/features/tag/graphql/index.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.field.resolver.js +10 -0
- package/server/dist/features/tag/graphql/tag.field.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.mutation.resolver.js +15 -0
- package/server/dist/features/tag/graphql/tag.mutation.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.query.resolver.js +26 -0
- package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.resolver.js +12 -0
- package/server/dist/features/tag/graphql/tag.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.type-defs.js +46 -0
- package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -0
- package/server/dist/{views/tag.js → features/tag/http/mcp.js} +3 -3
- package/server/dist/features/tag/http/mcp.js.map +1 -0
- package/server/dist/{modules/tag-organization.js → features/tag/services/organization.js} +38 -8
- package/server/dist/features/tag/services/organization.js.map +1 -0
- package/server/dist/features/view/graphql/index.js +11 -0
- package/server/dist/features/view/graphql/index.js.map +1 -0
- package/server/dist/features/view/graphql/view.mutation.resolver.js +53 -0
- package/server/dist/features/view/graphql/view.mutation.resolver.js.map +1 -0
- package/server/dist/features/view/graphql/view.query.resolver.js +29 -0
- package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -0
- package/server/dist/features/view/graphql/view.resolver.js +10 -0
- package/server/dist/features/view/graphql/view.resolver.js.map +1 -0
- package/server/dist/features/view/graphql/view.type-defs.js +63 -0
- package/server/dist/features/view/graphql/view.type-defs.js.map +1 -0
- package/server/dist/features/view/services/workspace.js +432 -0
- package/server/dist/features/view/services/workspace.js.map +1 -0
- package/server/dist/main.js +14 -0
- package/server/dist/main.js.map +1 -1
- package/server/dist/modules/auth-guard.js +16 -21
- package/server/dist/modules/auth-guard.js.map +1 -1
- package/server/dist/modules/auth-mode.js +16 -41
- package/server/dist/modules/auth-mode.js.map +1 -1
- package/server/dist/modules/blocknote.js +8 -17
- package/server/dist/modules/blocknote.js.map +1 -1
- package/server/dist/modules/data-maintenance.js +75 -0
- package/server/dist/modules/data-maintenance.js.map +1 -0
- package/server/dist/modules/error-handler.js.map +1 -1
- package/server/dist/modules/logger.js +5 -23
- package/server/dist/modules/logger.js.map +1 -1
- package/server/dist/modules/mcp-auth.js +4 -5
- package/server/dist/modules/mcp-auth.js.map +1 -1
- package/server/dist/modules/mcp-token.js.map +1 -1
- package/server/dist/modules/prisma-runtime.js +2 -7
- package/server/dist/modules/prisma-runtime.js.map +1 -1
- package/server/dist/modules/recovery-retention.js +1 -1
- package/server/dist/modules/recovery-retention.js.map +1 -1
- package/server/dist/modules/server-events-handler.js +34 -0
- package/server/dist/modules/server-events-handler.js.map +1 -0
- package/server/dist/modules/server-events.js +32 -0
- package/server/dist/modules/server-events.js.map +1 -0
- package/server/dist/modules/use-async.js.map +1 -1
- package/server/dist/paths.js.map +1 -1
- package/server/dist/routes/api.js +34 -0
- package/server/dist/routes/api.js.map +1 -0
- package/server/dist/routes/auth-pages.js +11 -0
- package/server/dist/routes/auth-pages.js.map +1 -0
- package/server/dist/routes/client.js +27 -0
- package/server/dist/routes/client.js.map +1 -0
- package/server/dist/routes/graphql.js +33 -0
- package/server/dist/routes/graphql.js.map +1 -0
- package/server/dist/routes/index.js +11 -0
- package/server/dist/routes/index.js.map +1 -0
- package/server/dist/routes/mcp.js +30 -0
- package/server/dist/routes/mcp.js.map +1 -0
- package/server/dist/schema/index.js +11 -8
- package/server/dist/schema/index.js.map +1 -1
- package/server/dist/types/index.js.map +1 -1
- package/server/prisma/migrations/20260413143000_0014_tag_name_unique/migration.sql +32 -0
- package/server/prisma/migrations/20260414143000_0015_note_searchable_text/migration.sql +5 -0
- package/server/prisma/migrations/20260416090000_0016_view_workspace/migration.sql +50 -0
- package/server/prisma/schema.prisma +66 -12
- package/server/client/dist/assets/Calendar-Kjy1PX6L.js +0 -25
- package/server/client/dist/assets/Graph-Bl5K2qUw.js +0 -1
- package/server/client/dist/assets/ModalActionRow-DyOb58KJ.js +0 -1
- package/server/client/dist/assets/Note-BfGma2Qc.css +0 -1
- package/server/client/dist/assets/Note-DN_ko2M3.js +0 -1
- package/server/client/dist/assets/Reminders-BK-Uv9Ps.js +0 -1
- package/server/client/dist/assets/Search-ClHeWvmI.js +0 -1
- package/server/client/dist/assets/Tag-Dph4V_kl.js +0 -1
- package/server/client/dist/assets/TagNotes-CxP5qHZj.js +0 -1
- package/server/client/dist/assets/index-6lIw6jYW.js +0 -1
- package/server/client/dist/assets/index-Ba2XPRI8.js +0 -205
- package/server/client/dist/assets/manage-image-Br_kKxbb.js +0 -1
- package/server/client/dist/assets/manage-image-detail-9DzqbiqI.js +0 -1
- package/server/client/dist/assets/mcp-C_So91eX.js +0 -1
- package/server/client/dist/assets/trash-Cg3nJ1m5.js +0 -1
- package/server/dist/modules/auth.js +0 -25
- package/server/dist/modules/auth.js.map +0 -1
- package/server/dist/modules/image-upload.js.map +0 -1
- package/server/dist/modules/mcp-admin.js.map +0 -1
- package/server/dist/modules/note-authoring.js.map +0 -1
- package/server/dist/modules/note-cleanup.js.map +0 -1
- package/server/dist/modules/note-snapshot.js.map +0 -1
- package/server/dist/modules/note-tag-filter.js.map +0 -1
- package/server/dist/modules/note-trash.js.map +0 -1
- package/server/dist/modules/remote-image.js.map +0 -1
- package/server/dist/modules/tag-organization.js.map +0 -1
- package/server/dist/schema/cache/index.js +0 -58
- package/server/dist/schema/cache/index.js.map +0 -1
- package/server/dist/schema/image/index.js +0 -88
- package/server/dist/schema/image/index.js.map +0 -1
- package/server/dist/schema/note/index.js +0 -582
- package/server/dist/schema/note/index.js.map +0 -1
- package/server/dist/schema/placeholder/index.js +0 -105
- package/server/dist/schema/placeholder/index.js.map +0 -1
- package/server/dist/schema/reminder/index.js +0 -161
- package/server/dist/schema/reminder/index.js.map +0 -1
- package/server/dist/schema/tag/index.js +0 -81
- package/server/dist/schema/tag/index.js.map +0 -1
- package/server/dist/urls.js +0 -9
- package/server/dist/urls.js.map +0 -1
- package/server/dist/views/auth.js +0 -305
- package/server/dist/views/auth.js.map +0 -1
- package/server/dist/views/image.js +0 -44
- package/server/dist/views/image.js.map +0 -1
- package/server/dist/views/index.js +0 -6
- package/server/dist/views/index.js.map +0 -1
- package/server/dist/views/mcp-admin.js.map +0 -1
- package/server/dist/views/note.js.map +0 -1
- package/server/dist/views/tag.js.map +0 -1
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { buildNoteTagNamesWhere } from "../../../features/note/services/tag-filter.js";
|
|
2
|
+
import models from "../../../models.js";
|
|
3
|
+
const VIEW_WORKSPACE_ID = 1;
|
|
4
|
+
const DEFAULT_VIEW_SECTION_LIMIT = 5;
|
|
5
|
+
const MIN_VIEW_SECTION_LIMIT = 1;
|
|
6
|
+
const MAX_VIEW_SECTION_LIMIT = 20;
|
|
7
|
+
const orderedViewSectionTagsInclude = {
|
|
8
|
+
tags: {
|
|
9
|
+
orderBy: [{ order: "asc" }, { createdAt: "asc" }]
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const orderedViewTabSectionsInclude = {
|
|
13
|
+
sections: {
|
|
14
|
+
orderBy: [{ order: "asc" }, { createdAt: "asc" }],
|
|
15
|
+
include: orderedViewSectionTagsInclude
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const normalizeTagName = (value) => {
|
|
19
|
+
const trimmedValue = value.trim();
|
|
20
|
+
if (!trimmedValue) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
if (trimmedValue.startsWith("@")) {
|
|
24
|
+
return trimmedValue;
|
|
25
|
+
}
|
|
26
|
+
if (trimmedValue.startsWith("#")) {
|
|
27
|
+
return `@${trimmedValue.slice(1)}`;
|
|
28
|
+
}
|
|
29
|
+
return `@${trimmedValue}`;
|
|
30
|
+
};
|
|
31
|
+
const normalizeViewTagNames = (values) => {
|
|
32
|
+
const normalizedTagNames = values.flatMap((value) => value.split(","));
|
|
33
|
+
return Array.from(new Set(normalizedTagNames.map(normalizeTagName).filter(Boolean)));
|
|
34
|
+
};
|
|
35
|
+
const clampViewSectionLimit = (value) => {
|
|
36
|
+
const numericValue = typeof value === "number" ? value : Number.NaN;
|
|
37
|
+
if (!Number.isInteger(numericValue)) {
|
|
38
|
+
return DEFAULT_VIEW_SECTION_LIMIT;
|
|
39
|
+
}
|
|
40
|
+
return Math.min(MAX_VIEW_SECTION_LIMIT, Math.max(MIN_VIEW_SECTION_LIMIT, numericValue));
|
|
41
|
+
};
|
|
42
|
+
const normalizeViewTabTitle = (title) => {
|
|
43
|
+
const trimmedTitle = title.trim();
|
|
44
|
+
return trimmedTitle || "Untitled View";
|
|
45
|
+
};
|
|
46
|
+
const buildDefaultSectionTitle = (tagNames) => {
|
|
47
|
+
if (tagNames.length === 0) {
|
|
48
|
+
return "Tagged notes";
|
|
49
|
+
}
|
|
50
|
+
return tagNames.slice(0, 2).join(" + ");
|
|
51
|
+
};
|
|
52
|
+
const normalizeViewSectionInput = (input) => {
|
|
53
|
+
const tagNames = normalizeViewTagNames(input.tagNames);
|
|
54
|
+
if (tagNames.length === 0) {
|
|
55
|
+
throw new Error("A view section requires at least one tag.");
|
|
56
|
+
}
|
|
57
|
+
const trimmedTitle = input.title?.trim() ?? "";
|
|
58
|
+
return {
|
|
59
|
+
title: trimmedTitle || buildDefaultSectionTitle(tagNames),
|
|
60
|
+
tagNames,
|
|
61
|
+
mode: input.mode === "or" ? "or" : "and",
|
|
62
|
+
limit: clampViewSectionLimit(input.limit)
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const pickNextActiveViewTabId = (tabIds, deletedTabId, currentActiveTabId) => {
|
|
66
|
+
const remainingTabIds = tabIds.filter((tabId) => tabId !== deletedTabId);
|
|
67
|
+
if (remainingTabIds.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (currentActiveTabId !== deletedTabId) {
|
|
71
|
+
return currentActiveTabId;
|
|
72
|
+
}
|
|
73
|
+
return remainingTabIds[0] ?? null;
|
|
74
|
+
};
|
|
75
|
+
const ensureValidReorderIds = (currentIds, nextIds) => {
|
|
76
|
+
if (currentIds.length !== nextIds.length) {
|
|
77
|
+
throw new Error("Reorder payload must include every item exactly once.");
|
|
78
|
+
}
|
|
79
|
+
const currentIdSet = new Set(currentIds);
|
|
80
|
+
const nextIdSet = new Set(nextIds);
|
|
81
|
+
if (currentIdSet.size !== currentIds.length || nextIdSet.size !== nextIds.length) {
|
|
82
|
+
throw new Error("Reorder payload contains duplicate ids.");
|
|
83
|
+
}
|
|
84
|
+
for (const id of currentIdSet) {
|
|
85
|
+
if (!nextIdSet.has(id)) {
|
|
86
|
+
throw new Error("Reorder payload must match the current item set.");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const parseViewId = (id) => {
|
|
91
|
+
const numericId = Number(id);
|
|
92
|
+
if (!Number.isInteger(numericId) || numericId <= 0) {
|
|
93
|
+
throw new Error("A valid id is required.");
|
|
94
|
+
}
|
|
95
|
+
return numericId;
|
|
96
|
+
};
|
|
97
|
+
const serializeViewSection = (section) => ({
|
|
98
|
+
id: String(section.id),
|
|
99
|
+
tabId: String(section.tabId),
|
|
100
|
+
title: section.title,
|
|
101
|
+
tagNames: section.tags.map((tag) => tag.name),
|
|
102
|
+
mode: section.mode,
|
|
103
|
+
limit: section.limit,
|
|
104
|
+
order: section.order
|
|
105
|
+
});
|
|
106
|
+
const serializeViewTab = (tab) => ({
|
|
107
|
+
id: String(tab.id),
|
|
108
|
+
title: tab.title,
|
|
109
|
+
order: tab.order,
|
|
110
|
+
sections: tab.sections.map(serializeViewSection)
|
|
111
|
+
});
|
|
112
|
+
const getOrderedViewTabs = async (db) => {
|
|
113
|
+
return db.viewTab.findMany({
|
|
114
|
+
orderBy: [{ order: "asc" }, { createdAt: "asc" }],
|
|
115
|
+
include: orderedViewTabSectionsInclude
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
const ensureViewWorkspace = async (db) => {
|
|
119
|
+
return db.viewWorkspace.upsert({
|
|
120
|
+
where: { id: VIEW_WORKSPACE_ID },
|
|
121
|
+
update: {},
|
|
122
|
+
create: { id: VIEW_WORKSPACE_ID }
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
const readViewWorkspace = async (db) => {
|
|
126
|
+
const [workspace, tabs] = await Promise.all([ensureViewWorkspace(db), getOrderedViewTabs(db)]);
|
|
127
|
+
const activeTabId = workspace.activeTabId !== null && tabs.some((tab) => tab.id === workspace.activeTabId) ? String(workspace.activeTabId) : tabs[0] ? String(tabs[0].id) : null;
|
|
128
|
+
return {
|
|
129
|
+
activeTabId,
|
|
130
|
+
tabs: tabs.map(serializeViewTab)
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
const readViewSection = async (db, id) => {
|
|
134
|
+
const section = await db.viewSection.findUnique({
|
|
135
|
+
where: { id },
|
|
136
|
+
include: orderedViewSectionTagsInclude
|
|
137
|
+
});
|
|
138
|
+
return section ? serializeViewSection(section) : null;
|
|
139
|
+
};
|
|
140
|
+
const getNextTabOrder = async (db) => {
|
|
141
|
+
const lastTab = await db.viewTab.findFirst({
|
|
142
|
+
orderBy: [{ order: "desc" }, { createdAt: "desc" }],
|
|
143
|
+
select: { order: true }
|
|
144
|
+
});
|
|
145
|
+
return lastTab ? lastTab.order + 1 : 0;
|
|
146
|
+
};
|
|
147
|
+
const getNextSectionOrder = async (db, tabId) => {
|
|
148
|
+
const lastSection = await db.viewSection.findFirst({
|
|
149
|
+
where: { tabId },
|
|
150
|
+
orderBy: [{ order: "desc" }, { createdAt: "desc" }],
|
|
151
|
+
select: { order: true }
|
|
152
|
+
});
|
|
153
|
+
return lastSection ? lastSection.order + 1 : 0;
|
|
154
|
+
};
|
|
155
|
+
const resequenceViewTabs = async (db) => {
|
|
156
|
+
const tabs = await db.viewTab.findMany({
|
|
157
|
+
orderBy: [{ order: "asc" }, { createdAt: "asc" }],
|
|
158
|
+
select: { id: true }
|
|
159
|
+
});
|
|
160
|
+
await Promise.all(
|
|
161
|
+
tabs.map(
|
|
162
|
+
(tab, index) => db.viewTab.update({
|
|
163
|
+
where: { id: tab.id },
|
|
164
|
+
data: { order: index }
|
|
165
|
+
})
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
const resequenceViewSections = async (db, tabId) => {
|
|
170
|
+
const sections = await db.viewSection.findMany({
|
|
171
|
+
where: { tabId },
|
|
172
|
+
orderBy: [{ order: "asc" }, { createdAt: "asc" }],
|
|
173
|
+
select: { id: true }
|
|
174
|
+
});
|
|
175
|
+
await Promise.all(
|
|
176
|
+
sections.map(
|
|
177
|
+
(section, index) => db.viewSection.update({
|
|
178
|
+
where: { id: section.id },
|
|
179
|
+
data: { order: index }
|
|
180
|
+
})
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
const getViewWorkspace = async () => {
|
|
185
|
+
return readViewWorkspace(models);
|
|
186
|
+
};
|
|
187
|
+
const getViewSectionById = async (id) => {
|
|
188
|
+
return readViewSection(models, parseViewId(id));
|
|
189
|
+
};
|
|
190
|
+
const getViewSectionNotes = async (id, pagination) => {
|
|
191
|
+
const section = await models.viewSection.findUnique({
|
|
192
|
+
where: { id: parseViewId(id) },
|
|
193
|
+
include: orderedViewSectionTagsInclude
|
|
194
|
+
});
|
|
195
|
+
if (!section) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const tagNames = section.tags.map((tag) => tag.name);
|
|
199
|
+
if (tagNames.length === 0) {
|
|
200
|
+
return {
|
|
201
|
+
totalCount: 0,
|
|
202
|
+
notes: []
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const where = buildNoteTagNamesWhere(tagNames, section.mode);
|
|
206
|
+
const [totalCount, notes] = await Promise.all([
|
|
207
|
+
models.note.count({ where }),
|
|
208
|
+
models.note.findMany({
|
|
209
|
+
orderBy: { updatedAt: "desc" },
|
|
210
|
+
where,
|
|
211
|
+
take: Number(pagination.limit),
|
|
212
|
+
skip: Number(pagination.offset)
|
|
213
|
+
})
|
|
214
|
+
]);
|
|
215
|
+
return {
|
|
216
|
+
totalCount,
|
|
217
|
+
notes
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
const createViewTab = async (title) => {
|
|
221
|
+
return models.$transaction(async (tx) => {
|
|
222
|
+
const workspace = await ensureViewWorkspace(tx);
|
|
223
|
+
const nextTabOrder = await getNextTabOrder(tx);
|
|
224
|
+
const createdTab = await tx.viewTab.create({
|
|
225
|
+
data: {
|
|
226
|
+
title: normalizeViewTabTitle(title),
|
|
227
|
+
order: nextTabOrder
|
|
228
|
+
},
|
|
229
|
+
include: orderedViewTabSectionsInclude
|
|
230
|
+
});
|
|
231
|
+
if (workspace.activeTabId === null) {
|
|
232
|
+
await tx.viewWorkspace.update({
|
|
233
|
+
where: { id: VIEW_WORKSPACE_ID },
|
|
234
|
+
data: { activeTabId: createdTab.id }
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return serializeViewTab(createdTab);
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
const updateViewTab = async (id, title) => {
|
|
241
|
+
const updatedTab = await models.viewTab.update({
|
|
242
|
+
where: { id: parseViewId(id) },
|
|
243
|
+
data: {
|
|
244
|
+
title: normalizeViewTabTitle(title)
|
|
245
|
+
},
|
|
246
|
+
include: orderedViewTabSectionsInclude
|
|
247
|
+
});
|
|
248
|
+
return serializeViewTab(updatedTab);
|
|
249
|
+
};
|
|
250
|
+
const deleteViewTab = async (id) => {
|
|
251
|
+
const tabId = parseViewId(id);
|
|
252
|
+
return models.$transaction(async (tx) => {
|
|
253
|
+
const workspace = await ensureViewWorkspace(tx);
|
|
254
|
+
const tabs = await tx.viewTab.findMany({
|
|
255
|
+
orderBy: [{ order: "asc" }, { createdAt: "asc" }],
|
|
256
|
+
select: { id: true }
|
|
257
|
+
});
|
|
258
|
+
if (!tabs.some((tab) => tab.id === tabId)) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
await tx.viewTab.delete({ where: { id: tabId } });
|
|
262
|
+
await resequenceViewTabs(tx);
|
|
263
|
+
const nextActiveTabId = pickNextActiveViewTabId(
|
|
264
|
+
tabs.map((tab) => tab.id),
|
|
265
|
+
tabId,
|
|
266
|
+
workspace.activeTabId
|
|
267
|
+
);
|
|
268
|
+
await tx.viewWorkspace.update({
|
|
269
|
+
where: { id: VIEW_WORKSPACE_ID },
|
|
270
|
+
data: { activeTabId: nextActiveTabId }
|
|
271
|
+
});
|
|
272
|
+
return true;
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
const setActiveViewTab = async (id) => {
|
|
276
|
+
const tabId = parseViewId(id);
|
|
277
|
+
return models.$transaction(async (tx) => {
|
|
278
|
+
const workspace = await ensureViewWorkspace(tx);
|
|
279
|
+
const tab = await tx.viewTab.findUnique({
|
|
280
|
+
where: { id: tabId },
|
|
281
|
+
select: { id: true }
|
|
282
|
+
});
|
|
283
|
+
if (!tab) {
|
|
284
|
+
throw new Error("View tab not found.");
|
|
285
|
+
}
|
|
286
|
+
await tx.viewWorkspace.update({
|
|
287
|
+
where: { id: workspace.id },
|
|
288
|
+
data: { activeTabId: tabId }
|
|
289
|
+
});
|
|
290
|
+
return readViewWorkspace(tx);
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
const reorderViewTabs = async (tabIds) => {
|
|
294
|
+
const nextTabIds = tabIds.map(parseViewId);
|
|
295
|
+
return models.$transaction(async (tx) => {
|
|
296
|
+
const tabs = await tx.viewTab.findMany({
|
|
297
|
+
orderBy: [{ order: "asc" }, { createdAt: "asc" }],
|
|
298
|
+
select: { id: true }
|
|
299
|
+
});
|
|
300
|
+
const currentTabIds = tabs.map((tab) => tab.id);
|
|
301
|
+
ensureValidReorderIds(currentTabIds, nextTabIds);
|
|
302
|
+
await Promise.all(
|
|
303
|
+
nextTabIds.map(
|
|
304
|
+
(tabId, index) => tx.viewTab.update({
|
|
305
|
+
where: { id: tabId },
|
|
306
|
+
data: { order: index }
|
|
307
|
+
})
|
|
308
|
+
)
|
|
309
|
+
);
|
|
310
|
+
const reorderedTabs = await getOrderedViewTabs(tx);
|
|
311
|
+
return reorderedTabs.map(serializeViewTab);
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
const createViewSection = async (tabId, input) => {
|
|
315
|
+
const numericTabId = parseViewId(tabId);
|
|
316
|
+
const nextSection = normalizeViewSectionInput(input);
|
|
317
|
+
return models.$transaction(async (tx) => {
|
|
318
|
+
const tab = await tx.viewTab.findUnique({
|
|
319
|
+
where: { id: numericTabId },
|
|
320
|
+
select: { id: true }
|
|
321
|
+
});
|
|
322
|
+
if (!tab) {
|
|
323
|
+
throw new Error("View tab not found.");
|
|
324
|
+
}
|
|
325
|
+
const nextSectionOrder = await getNextSectionOrder(tx, numericTabId);
|
|
326
|
+
const createdSection = await tx.viewSection.create({
|
|
327
|
+
data: {
|
|
328
|
+
tabId: numericTabId,
|
|
329
|
+
title: nextSection.title,
|
|
330
|
+
mode: nextSection.mode,
|
|
331
|
+
limit: nextSection.limit,
|
|
332
|
+
order: nextSectionOrder,
|
|
333
|
+
tags: {
|
|
334
|
+
create: nextSection.tagNames.map((tagName, index) => ({
|
|
335
|
+
name: tagName,
|
|
336
|
+
order: index
|
|
337
|
+
}))
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
include: orderedViewSectionTagsInclude
|
|
341
|
+
});
|
|
342
|
+
return serializeViewSection(createdSection);
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
const updateViewSection = async (id, input) => {
|
|
346
|
+
const sectionId = parseViewId(id);
|
|
347
|
+
const nextSection = normalizeViewSectionInput(input);
|
|
348
|
+
const updatedSection = await models.viewSection.update({
|
|
349
|
+
where: { id: sectionId },
|
|
350
|
+
data: {
|
|
351
|
+
title: nextSection.title,
|
|
352
|
+
mode: nextSection.mode,
|
|
353
|
+
limit: nextSection.limit,
|
|
354
|
+
tags: {
|
|
355
|
+
deleteMany: {},
|
|
356
|
+
create: nextSection.tagNames.map((tagName, index) => ({
|
|
357
|
+
name: tagName,
|
|
358
|
+
order: index
|
|
359
|
+
}))
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
include: orderedViewSectionTagsInclude
|
|
363
|
+
});
|
|
364
|
+
return serializeViewSection(updatedSection);
|
|
365
|
+
};
|
|
366
|
+
const deleteViewSection = async (id) => {
|
|
367
|
+
const sectionId = parseViewId(id);
|
|
368
|
+
return models.$transaction(async (tx) => {
|
|
369
|
+
const section = await tx.viewSection.findUnique({
|
|
370
|
+
where: { id: sectionId },
|
|
371
|
+
select: { id: true, tabId: true }
|
|
372
|
+
});
|
|
373
|
+
if (!section) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
await tx.viewSection.delete({
|
|
377
|
+
where: { id: sectionId }
|
|
378
|
+
});
|
|
379
|
+
await resequenceViewSections(tx, section.tabId);
|
|
380
|
+
return true;
|
|
381
|
+
});
|
|
382
|
+
};
|
|
383
|
+
const reorderViewSections = async (tabId, sectionIds) => {
|
|
384
|
+
const numericTabId = parseViewId(tabId);
|
|
385
|
+
const nextSectionIds = sectionIds.map(parseViewId);
|
|
386
|
+
return models.$transaction(async (tx) => {
|
|
387
|
+
const sections = await tx.viewSection.findMany({
|
|
388
|
+
where: { tabId: numericTabId },
|
|
389
|
+
orderBy: [{ order: "asc" }, { createdAt: "asc" }],
|
|
390
|
+
select: { id: true }
|
|
391
|
+
});
|
|
392
|
+
const currentSectionIds = sections.map((section) => section.id);
|
|
393
|
+
ensureValidReorderIds(currentSectionIds, nextSectionIds);
|
|
394
|
+
await Promise.all(
|
|
395
|
+
nextSectionIds.map(
|
|
396
|
+
(sectionId, index) => tx.viewSection.update({
|
|
397
|
+
where: { id: sectionId },
|
|
398
|
+
data: { order: index }
|
|
399
|
+
})
|
|
400
|
+
)
|
|
401
|
+
);
|
|
402
|
+
const reorderedSections = await tx.viewSection.findMany({
|
|
403
|
+
where: { tabId: numericTabId },
|
|
404
|
+
orderBy: [{ order: "asc" }, { createdAt: "asc" }],
|
|
405
|
+
include: orderedViewSectionTagsInclude
|
|
406
|
+
});
|
|
407
|
+
return reorderedSections.map(serializeViewSection);
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
export {
|
|
411
|
+
DEFAULT_VIEW_SECTION_LIMIT,
|
|
412
|
+
MAX_VIEW_SECTION_LIMIT,
|
|
413
|
+
MIN_VIEW_SECTION_LIMIT,
|
|
414
|
+
clampViewSectionLimit,
|
|
415
|
+
createViewSection,
|
|
416
|
+
createViewTab,
|
|
417
|
+
deleteViewSection,
|
|
418
|
+
deleteViewTab,
|
|
419
|
+
getViewSectionById,
|
|
420
|
+
getViewSectionNotes,
|
|
421
|
+
getViewWorkspace,
|
|
422
|
+
normalizeViewSectionInput,
|
|
423
|
+
normalizeViewTabTitle,
|
|
424
|
+
normalizeViewTagNames,
|
|
425
|
+
pickNextActiveViewTabId,
|
|
426
|
+
reorderViewSections,
|
|
427
|
+
reorderViewTabs,
|
|
428
|
+
setActiveViewTab,
|
|
429
|
+
updateViewSection,
|
|
430
|
+
updateViewTab
|
|
431
|
+
};
|
|
432
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/view/services/workspace.ts"],"sourcesContent":["import type { Note, Prisma } from '@prisma/client';\nimport { buildNoteTagNamesWhere, type NoteTagMatchMode } from '~/features/note/services/tag-filter.js';\nimport models from '~/models.js';\n\nexport type ViewTagMatchMode = NoteTagMatchMode;\n\nexport interface ViewSectionRecord {\n id: string;\n tabId: string;\n title: string;\n tagNames: string[];\n mode: ViewTagMatchMode;\n limit: number;\n order: number;\n}\n\nexport interface ViewTabRecord {\n id: string;\n title: string;\n order: number;\n sections: ViewSectionRecord[];\n}\n\nexport interface ViewWorkspaceRecord {\n activeTabId: string | null;\n tabs: ViewTabRecord[];\n}\n\nexport interface ViewSectionInput {\n title?: string;\n tagNames: string[];\n mode?: ViewTagMatchMode;\n limit?: number;\n}\n\nexport interface ViewSectionNotesResult {\n totalCount: number;\n notes: Note[];\n}\n\nconst VIEW_WORKSPACE_ID = 1;\n\nexport const DEFAULT_VIEW_SECTION_LIMIT = 5;\nexport const MIN_VIEW_SECTION_LIMIT = 1;\nexport const MAX_VIEW_SECTION_LIMIT = 20;\n\ntype ViewDbClient = typeof models | Prisma.TransactionClient;\n\nconst orderedViewSectionTagsInclude = {\n tags: {\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n },\n} satisfies Prisma.ViewSectionInclude;\n\nconst orderedViewTabSectionsInclude = {\n sections: {\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n include: orderedViewSectionTagsInclude,\n },\n} satisfies Prisma.ViewTabInclude;\n\ntype DbViewSection = Prisma.ViewSectionGetPayload<{\n include: typeof orderedViewSectionTagsInclude;\n}>;\n\ntype DbViewTab = Prisma.ViewTabGetPayload<{\n include: typeof orderedViewTabSectionsInclude;\n}>;\n\nconst normalizeTagName = (value: string) => {\n const trimmedValue = value.trim();\n\n if (!trimmedValue) {\n return '';\n }\n\n if (trimmedValue.startsWith('@')) {\n return trimmedValue;\n }\n\n if (trimmedValue.startsWith('#')) {\n return `@${trimmedValue.slice(1)}`;\n }\n\n return `@${trimmedValue}`;\n};\n\nexport const normalizeViewTagNames = (values: string[]) => {\n const normalizedTagNames = values.flatMap((value) => value.split(','));\n\n return Array.from(new Set(normalizedTagNames.map(normalizeTagName).filter(Boolean)));\n};\n\nexport const clampViewSectionLimit = (value: number | undefined) => {\n const numericValue = typeof value === 'number' ? value : Number.NaN;\n\n if (!Number.isInteger(numericValue)) {\n return DEFAULT_VIEW_SECTION_LIMIT;\n }\n\n return Math.min(MAX_VIEW_SECTION_LIMIT, Math.max(MIN_VIEW_SECTION_LIMIT, numericValue));\n};\n\nexport const normalizeViewTabTitle = (title: string) => {\n const trimmedTitle = title.trim();\n\n return trimmedTitle || 'Untitled View';\n};\n\nconst buildDefaultSectionTitle = (tagNames: string[]) => {\n if (tagNames.length === 0) {\n return 'Tagged notes';\n }\n\n return tagNames.slice(0, 2).join(' + ');\n};\n\nexport const normalizeViewSectionInput = (input: ViewSectionInput) => {\n const tagNames = normalizeViewTagNames(input.tagNames);\n\n if (tagNames.length === 0) {\n throw new Error('A view section requires at least one tag.');\n }\n\n const trimmedTitle = input.title?.trim() ?? '';\n\n return {\n title: trimmedTitle || buildDefaultSectionTitle(tagNames),\n tagNames,\n mode: input.mode === 'or' ? 'or' : 'and',\n limit: clampViewSectionLimit(input.limit),\n } satisfies {\n title: string;\n tagNames: string[];\n mode: ViewTagMatchMode;\n limit: number;\n };\n};\n\nexport const pickNextActiveViewTabId = (tabIds: number[], deletedTabId: number, currentActiveTabId: number | null) => {\n const remainingTabIds = tabIds.filter((tabId) => tabId !== deletedTabId);\n\n if (remainingTabIds.length === 0) {\n return null;\n }\n\n if (currentActiveTabId !== deletedTabId) {\n return currentActiveTabId;\n }\n\n return remainingTabIds[0] ?? null;\n};\n\nconst ensureValidReorderIds = (currentIds: number[], nextIds: number[]) => {\n if (currentIds.length !== nextIds.length) {\n throw new Error('Reorder payload must include every item exactly once.');\n }\n\n const currentIdSet = new Set(currentIds);\n const nextIdSet = new Set(nextIds);\n\n if (currentIdSet.size !== currentIds.length || nextIdSet.size !== nextIds.length) {\n throw new Error('Reorder payload contains duplicate ids.');\n }\n\n for (const id of currentIdSet) {\n if (!nextIdSet.has(id)) {\n throw new Error('Reorder payload must match the current item set.');\n }\n }\n};\n\nconst parseViewId = (id: string) => {\n const numericId = Number(id);\n\n if (!Number.isInteger(numericId) || numericId <= 0) {\n throw new Error('A valid id is required.');\n }\n\n return numericId;\n};\n\nconst serializeViewSection = (section: DbViewSection): ViewSectionRecord => ({\n id: String(section.id),\n tabId: String(section.tabId),\n title: section.title,\n tagNames: section.tags.map((tag) => tag.name),\n mode: section.mode as ViewTagMatchMode,\n limit: section.limit,\n order: section.order,\n});\n\nconst serializeViewTab = (tab: DbViewTab): ViewTabRecord => ({\n id: String(tab.id),\n title: tab.title,\n order: tab.order,\n sections: tab.sections.map(serializeViewSection),\n});\n\nconst getOrderedViewTabs = async (db: ViewDbClient) => {\n return db.viewTab.findMany({\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n include: orderedViewTabSectionsInclude,\n });\n};\n\nconst ensureViewWorkspace = async (db: ViewDbClient) => {\n return db.viewWorkspace.upsert({\n where: { id: VIEW_WORKSPACE_ID },\n update: {},\n create: { id: VIEW_WORKSPACE_ID },\n });\n};\n\nconst readViewWorkspace = async (db: ViewDbClient): Promise<ViewWorkspaceRecord> => {\n const [workspace, tabs] = await Promise.all([ensureViewWorkspace(db), getOrderedViewTabs(db)]);\n\n const activeTabId =\n workspace.activeTabId !== null && tabs.some((tab) => tab.id === workspace.activeTabId)\n ? String(workspace.activeTabId)\n : tabs[0]\n ? String(tabs[0].id)\n : null;\n\n return {\n activeTabId,\n tabs: tabs.map(serializeViewTab),\n };\n};\n\nconst readViewSection = async (db: ViewDbClient, id: number): Promise<ViewSectionRecord | null> => {\n const section = await db.viewSection.findUnique({\n where: { id },\n include: orderedViewSectionTagsInclude,\n });\n\n return section ? serializeViewSection(section) : null;\n};\n\nconst getNextTabOrder = async (db: ViewDbClient) => {\n const lastTab = await db.viewTab.findFirst({\n orderBy: [{ order: 'desc' }, { createdAt: 'desc' }],\n select: { order: true },\n });\n\n return lastTab ? lastTab.order + 1 : 0;\n};\n\nconst getNextSectionOrder = async (db: ViewDbClient, tabId: number) => {\n const lastSection = await db.viewSection.findFirst({\n where: { tabId },\n orderBy: [{ order: 'desc' }, { createdAt: 'desc' }],\n select: { order: true },\n });\n\n return lastSection ? lastSection.order + 1 : 0;\n};\n\nconst resequenceViewTabs = async (db: ViewDbClient) => {\n const tabs = await db.viewTab.findMany({\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n\n await Promise.all(\n tabs.map((tab, index) =>\n db.viewTab.update({\n where: { id: tab.id },\n data: { order: index },\n }),\n ),\n );\n};\n\nconst resequenceViewSections = async (db: ViewDbClient, tabId: number) => {\n const sections = await db.viewSection.findMany({\n where: { tabId },\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n\n await Promise.all(\n sections.map((section, index) =>\n db.viewSection.update({\n where: { id: section.id },\n data: { order: index },\n }),\n ),\n );\n};\n\nexport const getViewWorkspace = async () => {\n return readViewWorkspace(models);\n};\n\nexport const getViewSectionById = async (id: string) => {\n return readViewSection(models, parseViewId(id));\n};\n\nexport const getViewSectionNotes = async (\n id: string,\n pagination: {\n limit: number;\n offset: number;\n },\n): Promise<ViewSectionNotesResult | null> => {\n const section = await models.viewSection.findUnique({\n where: { id: parseViewId(id) },\n include: orderedViewSectionTagsInclude,\n });\n\n if (!section) {\n return null;\n }\n\n const tagNames = section.tags.map((tag) => tag.name);\n\n if (tagNames.length === 0) {\n return {\n totalCount: 0,\n notes: [],\n };\n }\n\n const where = buildNoteTagNamesWhere(tagNames, section.mode as NoteTagMatchMode);\n\n const [totalCount, notes] = await Promise.all([\n models.note.count({ where }),\n models.note.findMany({\n orderBy: { updatedAt: 'desc' },\n where,\n take: Number(pagination.limit),\n skip: Number(pagination.offset),\n }),\n ]);\n\n return {\n totalCount,\n notes,\n };\n};\n\nexport const createViewTab = async (title: string) => {\n return models.$transaction(async (tx) => {\n const workspace = await ensureViewWorkspace(tx);\n const nextTabOrder = await getNextTabOrder(tx);\n const createdTab = await tx.viewTab.create({\n data: {\n title: normalizeViewTabTitle(title),\n order: nextTabOrder,\n },\n include: orderedViewTabSectionsInclude,\n });\n\n if (workspace.activeTabId === null) {\n await tx.viewWorkspace.update({\n where: { id: VIEW_WORKSPACE_ID },\n data: { activeTabId: createdTab.id },\n });\n }\n\n return serializeViewTab(createdTab);\n });\n};\n\nexport const updateViewTab = async (id: string, title: string) => {\n const updatedTab = await models.viewTab.update({\n where: { id: parseViewId(id) },\n data: {\n title: normalizeViewTabTitle(title),\n },\n include: orderedViewTabSectionsInclude,\n });\n\n return serializeViewTab(updatedTab);\n};\n\nexport const deleteViewTab = async (id: string) => {\n const tabId = parseViewId(id);\n\n return models.$transaction(async (tx) => {\n const workspace = await ensureViewWorkspace(tx);\n const tabs = await tx.viewTab.findMany({\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n\n if (!tabs.some((tab) => tab.id === tabId)) {\n return false;\n }\n\n await tx.viewTab.delete({ where: { id: tabId } });\n await resequenceViewTabs(tx);\n\n const nextActiveTabId = pickNextActiveViewTabId(\n tabs.map((tab) => tab.id),\n tabId,\n workspace.activeTabId,\n );\n\n await tx.viewWorkspace.update({\n where: { id: VIEW_WORKSPACE_ID },\n data: { activeTabId: nextActiveTabId },\n });\n\n return true;\n });\n};\n\nexport const setActiveViewTab = async (id: string) => {\n const tabId = parseViewId(id);\n\n return models.$transaction(async (tx) => {\n const workspace = await ensureViewWorkspace(tx);\n const tab = await tx.viewTab.findUnique({\n where: { id: tabId },\n select: { id: true },\n });\n\n if (!tab) {\n throw new Error('View tab not found.');\n }\n\n await tx.viewWorkspace.update({\n where: { id: workspace.id },\n data: { activeTabId: tabId },\n });\n\n return readViewWorkspace(tx);\n });\n};\n\nexport const reorderViewTabs = async (tabIds: string[]) => {\n const nextTabIds = tabIds.map(parseViewId);\n\n return models.$transaction(async (tx) => {\n const tabs = await tx.viewTab.findMany({\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n const currentTabIds = tabs.map((tab) => tab.id);\n\n ensureValidReorderIds(currentTabIds, nextTabIds);\n\n await Promise.all(\n nextTabIds.map((tabId, index) =>\n tx.viewTab.update({\n where: { id: tabId },\n data: { order: index },\n }),\n ),\n );\n\n const reorderedTabs = await getOrderedViewTabs(tx);\n return reorderedTabs.map(serializeViewTab);\n });\n};\n\nexport const createViewSection = async (tabId: string, input: ViewSectionInput) => {\n const numericTabId = parseViewId(tabId);\n const nextSection = normalizeViewSectionInput(input);\n\n return models.$transaction(async (tx) => {\n const tab = await tx.viewTab.findUnique({\n where: { id: numericTabId },\n select: { id: true },\n });\n\n if (!tab) {\n throw new Error('View tab not found.');\n }\n\n const nextSectionOrder = await getNextSectionOrder(tx, numericTabId);\n\n const createdSection = await tx.viewSection.create({\n data: {\n tabId: numericTabId,\n title: nextSection.title,\n mode: nextSection.mode,\n limit: nextSection.limit,\n order: nextSectionOrder,\n tags: {\n create: nextSection.tagNames.map((tagName, index) => ({\n name: tagName,\n order: index,\n })),\n },\n },\n include: orderedViewSectionTagsInclude,\n });\n\n return serializeViewSection(createdSection);\n });\n};\n\nexport const updateViewSection = async (id: string, input: ViewSectionInput) => {\n const sectionId = parseViewId(id);\n const nextSection = normalizeViewSectionInput(input);\n\n const updatedSection = await models.viewSection.update({\n where: { id: sectionId },\n data: {\n title: nextSection.title,\n mode: nextSection.mode,\n limit: nextSection.limit,\n tags: {\n deleteMany: {},\n create: nextSection.tagNames.map((tagName, index) => ({\n name: tagName,\n order: index,\n })),\n },\n },\n include: orderedViewSectionTagsInclude,\n });\n\n return serializeViewSection(updatedSection);\n};\n\nexport const deleteViewSection = async (id: string) => {\n const sectionId = parseViewId(id);\n\n return models.$transaction(async (tx) => {\n const section = await tx.viewSection.findUnique({\n where: { id: sectionId },\n select: { id: true, tabId: true },\n });\n\n if (!section) {\n return false;\n }\n\n await tx.viewSection.delete({\n where: { id: sectionId },\n });\n await resequenceViewSections(tx, section.tabId);\n\n return true;\n });\n};\n\nexport const reorderViewSections = async (tabId: string, sectionIds: string[]) => {\n const numericTabId = parseViewId(tabId);\n const nextSectionIds = sectionIds.map(parseViewId);\n\n return models.$transaction(async (tx) => {\n const sections = await tx.viewSection.findMany({\n where: { tabId: numericTabId },\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n select: { id: true },\n });\n const currentSectionIds = sections.map((section) => section.id);\n\n ensureValidReorderIds(currentSectionIds, nextSectionIds);\n\n await Promise.all(\n nextSectionIds.map((sectionId, index) =>\n tx.viewSection.update({\n where: { id: sectionId },\n data: { order: index },\n }),\n ),\n );\n\n const reorderedSections = await tx.viewSection.findMany({\n where: { tabId: numericTabId },\n orderBy: [{ order: 'asc' }, { createdAt: 'asc' }],\n include: orderedViewSectionTagsInclude,\n });\n\n return reorderedSections.map(serializeViewSection);\n });\n};\n"],"mappings":"AACA,SAAS,8BAAqD;AAC9D,OAAO,YAAY;AAsCnB,MAAM,oBAAoB;AAEnB,MAAM,6BAA6B;AACnC,MAAM,yBAAyB;AAC/B,MAAM,yBAAyB;AAItC,MAAM,gCAAgC;AAAA,EAClC,MAAM;AAAA,IACF,SAAS,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,EACpD;AACJ;AAEA,MAAM,gCAAgC;AAAA,EAClC,UAAU;AAAA,IACN,SAAS,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,IAChD,SAAS;AAAA,EACb;AACJ;AAUA,MAAM,mBAAmB,CAAC,UAAkB;AACxC,QAAM,eAAe,MAAM,KAAK;AAEhC,MAAI,CAAC,cAAc;AACf,WAAO;AAAA,EACX;AAEA,MAAI,aAAa,WAAW,GAAG,GAAG;AAC9B,WAAO;AAAA,EACX;AAEA,MAAI,aAAa,WAAW,GAAG,GAAG;AAC9B,WAAO,IAAI,aAAa,MAAM,CAAC,CAAC;AAAA,EACpC;AAEA,SAAO,IAAI,YAAY;AAC3B;AAEO,MAAM,wBAAwB,CAAC,WAAqB;AACvD,QAAM,qBAAqB,OAAO,QAAQ,CAAC,UAAU,MAAM,MAAM,GAAG,CAAC;AAErE,SAAO,MAAM,KAAK,IAAI,IAAI,mBAAmB,IAAI,gBAAgB,EAAE,OAAO,OAAO,CAAC,CAAC;AACvF;AAEO,MAAM,wBAAwB,CAAC,UAA8B;AAChE,QAAM,eAAe,OAAO,UAAU,WAAW,QAAQ,OAAO;AAEhE,MAAI,CAAC,OAAO,UAAU,YAAY,GAAG;AACjC,WAAO;AAAA,EACX;AAEA,SAAO,KAAK,IAAI,wBAAwB,KAAK,IAAI,wBAAwB,YAAY,CAAC;AAC1F;AAEO,MAAM,wBAAwB,CAAC,UAAkB;AACpD,QAAM,eAAe,MAAM,KAAK;AAEhC,SAAO,gBAAgB;AAC3B;AAEA,MAAM,2BAA2B,CAAC,aAAuB;AACrD,MAAI,SAAS,WAAW,GAAG;AACvB,WAAO;AAAA,EACX;AAEA,SAAO,SAAS,MAAM,GAAG,CAAC,EAAE,KAAK,KAAK;AAC1C;AAEO,MAAM,4BAA4B,CAAC,UAA4B;AAClE,QAAM,WAAW,sBAAsB,MAAM,QAAQ;AAErD,MAAI,SAAS,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC/D;AAEA,QAAM,eAAe,MAAM,OAAO,KAAK,KAAK;AAE5C,SAAO;AAAA,IACH,OAAO,gBAAgB,yBAAyB,QAAQ;AAAA,IACxD;AAAA,IACA,MAAM,MAAM,SAAS,OAAO,OAAO;AAAA,IACnC,OAAO,sBAAsB,MAAM,KAAK;AAAA,EAC5C;AAMJ;AAEO,MAAM,0BAA0B,CAAC,QAAkB,cAAsB,uBAAsC;AAClH,QAAM,kBAAkB,OAAO,OAAO,CAAC,UAAU,UAAU,YAAY;AAEvE,MAAI,gBAAgB,WAAW,GAAG;AAC9B,WAAO;AAAA,EACX;AAEA,MAAI,uBAAuB,cAAc;AACrC,WAAO;AAAA,EACX;AAEA,SAAO,gBAAgB,CAAC,KAAK;AACjC;AAEA,MAAM,wBAAwB,CAAC,YAAsB,YAAsB;AACvE,MAAI,WAAW,WAAW,QAAQ,QAAQ;AACtC,UAAM,IAAI,MAAM,uDAAuD;AAAA,EAC3E;AAEA,QAAM,eAAe,IAAI,IAAI,UAAU;AACvC,QAAM,YAAY,IAAI,IAAI,OAAO;AAEjC,MAAI,aAAa,SAAS,WAAW,UAAU,UAAU,SAAS,QAAQ,QAAQ;AAC9E,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC7D;AAEA,aAAW,MAAM,cAAc;AAC3B,QAAI,CAAC,UAAU,IAAI,EAAE,GAAG;AACpB,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACtE;AAAA,EACJ;AACJ;AAEA,MAAM,cAAc,CAAC,OAAe;AAChC,QAAM,YAAY,OAAO,EAAE;AAE3B,MAAI,CAAC,OAAO,UAAU,SAAS,KAAK,aAAa,GAAG;AAChD,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC7C;AAEA,SAAO;AACX;AAEA,MAAM,uBAAuB,CAAC,aAA+C;AAAA,EACzE,IAAI,OAAO,QAAQ,EAAE;AAAA,EACrB,OAAO,OAAO,QAAQ,KAAK;AAAA,EAC3B,OAAO,QAAQ;AAAA,EACf,UAAU,QAAQ,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI;AAAA,EAC5C,MAAM,QAAQ;AAAA,EACd,OAAO,QAAQ;AAAA,EACf,OAAO,QAAQ;AACnB;AAEA,MAAM,mBAAmB,CAAC,SAAmC;AAAA,EACzD,IAAI,OAAO,IAAI,EAAE;AAAA,EACjB,OAAO,IAAI;AAAA,EACX,OAAO,IAAI;AAAA,EACX,UAAU,IAAI,SAAS,IAAI,oBAAoB;AACnD;AAEA,MAAM,qBAAqB,OAAO,OAAqB;AACnD,SAAO,GAAG,QAAQ,SAAS;AAAA,IACvB,SAAS,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,IAChD,SAAS;AAAA,EACb,CAAC;AACL;AAEA,MAAM,sBAAsB,OAAO,OAAqB;AACpD,SAAO,GAAG,cAAc,OAAO;AAAA,IAC3B,OAAO,EAAE,IAAI,kBAAkB;AAAA,IAC/B,QAAQ,CAAC;AAAA,IACT,QAAQ,EAAE,IAAI,kBAAkB;AAAA,EACpC,CAAC;AACL;AAEA,MAAM,oBAAoB,OAAO,OAAmD;AAChF,QAAM,CAAC,WAAW,IAAI,IAAI,MAAM,QAAQ,IAAI,CAAC,oBAAoB,EAAE,GAAG,mBAAmB,EAAE,CAAC,CAAC;AAE7F,QAAM,cACF,UAAU,gBAAgB,QAAQ,KAAK,KAAK,CAAC,QAAQ,IAAI,OAAO,UAAU,WAAW,IAC/E,OAAO,UAAU,WAAW,IAC5B,KAAK,CAAC,IACJ,OAAO,KAAK,CAAC,EAAE,EAAE,IACjB;AAEZ,SAAO;AAAA,IACH;AAAA,IACA,MAAM,KAAK,IAAI,gBAAgB;AAAA,EACnC;AACJ;AAEA,MAAM,kBAAkB,OAAO,IAAkB,OAAkD;AAC/F,QAAM,UAAU,MAAM,GAAG,YAAY,WAAW;AAAA,IAC5C,OAAO,EAAE,GAAG;AAAA,IACZ,SAAS;AAAA,EACb,CAAC;AAED,SAAO,UAAU,qBAAqB,OAAO,IAAI;AACrD;AAEA,MAAM,kBAAkB,OAAO,OAAqB;AAChD,QAAM,UAAU,MAAM,GAAG,QAAQ,UAAU;AAAA,IACvC,SAAS,CAAC,EAAE,OAAO,OAAO,GAAG,EAAE,WAAW,OAAO,CAAC;AAAA,IAClD,QAAQ,EAAE,OAAO,KAAK;AAAA,EAC1B,CAAC;AAED,SAAO,UAAU,QAAQ,QAAQ,IAAI;AACzC;AAEA,MAAM,sBAAsB,OAAO,IAAkB,UAAkB;AACnE,QAAM,cAAc,MAAM,GAAG,YAAY,UAAU;AAAA,IAC/C,OAAO,EAAE,MAAM;AAAA,IACf,SAAS,CAAC,EAAE,OAAO,OAAO,GAAG,EAAE,WAAW,OAAO,CAAC;AAAA,IAClD,QAAQ,EAAE,OAAO,KAAK;AAAA,EAC1B,CAAC;AAED,SAAO,cAAc,YAAY,QAAQ,IAAI;AACjD;AAEA,MAAM,qBAAqB,OAAO,OAAqB;AACnD,QAAM,OAAO,MAAM,GAAG,QAAQ,SAAS;AAAA,IACnC,SAAS,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,IAChD,QAAQ,EAAE,IAAI,KAAK;AAAA,EACvB,CAAC;AAED,QAAM,QAAQ;AAAA,IACV,KAAK;AAAA,MAAI,CAAC,KAAK,UACX,GAAG,QAAQ,OAAO;AAAA,QACd,OAAO,EAAE,IAAI,IAAI,GAAG;AAAA,QACpB,MAAM,EAAE,OAAO,MAAM;AAAA,MACzB,CAAC;AAAA,IACL;AAAA,EACJ;AACJ;AAEA,MAAM,yBAAyB,OAAO,IAAkB,UAAkB;AACtE,QAAM,WAAW,MAAM,GAAG,YAAY,SAAS;AAAA,IAC3C,OAAO,EAAE,MAAM;AAAA,IACf,SAAS,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,IAChD,QAAQ,EAAE,IAAI,KAAK;AAAA,EACvB,CAAC;AAED,QAAM,QAAQ;AAAA,IACV,SAAS;AAAA,MAAI,CAAC,SAAS,UACnB,GAAG,YAAY,OAAO;AAAA,QAClB,OAAO,EAAE,IAAI,QAAQ,GAAG;AAAA,QACxB,MAAM,EAAE,OAAO,MAAM;AAAA,MACzB,CAAC;AAAA,IACL;AAAA,EACJ;AACJ;AAEO,MAAM,mBAAmB,YAAY;AACxC,SAAO,kBAAkB,MAAM;AACnC;AAEO,MAAM,qBAAqB,OAAO,OAAe;AACpD,SAAO,gBAAgB,QAAQ,YAAY,EAAE,CAAC;AAClD;AAEO,MAAM,sBAAsB,OAC/B,IACA,eAIyC;AACzC,QAAM,UAAU,MAAM,OAAO,YAAY,WAAW;AAAA,IAChD,OAAO,EAAE,IAAI,YAAY,EAAE,EAAE;AAAA,IAC7B,SAAS;AAAA,EACb,CAAC;AAED,MAAI,CAAC,SAAS;AACV,WAAO;AAAA,EACX;AAEA,QAAM,WAAW,QAAQ,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI;AAEnD,MAAI,SAAS,WAAW,GAAG;AACvB,WAAO;AAAA,MACH,YAAY;AAAA,MACZ,OAAO,CAAC;AAAA,IACZ;AAAA,EACJ;AAEA,QAAM,QAAQ,uBAAuB,UAAU,QAAQ,IAAwB;AAE/E,QAAM,CAAC,YAAY,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC1C,OAAO,KAAK,MAAM,EAAE,MAAM,CAAC;AAAA,IAC3B,OAAO,KAAK,SAAS;AAAA,MACjB,SAAS,EAAE,WAAW,OAAO;AAAA,MAC7B;AAAA,MACA,MAAM,OAAO,WAAW,KAAK;AAAA,MAC7B,MAAM,OAAO,WAAW,MAAM;AAAA,IAClC,CAAC;AAAA,EACL,CAAC;AAED,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;AAEO,MAAM,gBAAgB,OAAO,UAAkB;AAClD,SAAO,OAAO,aAAa,OAAO,OAAO;AACrC,UAAM,YAAY,MAAM,oBAAoB,EAAE;AAC9C,UAAM,eAAe,MAAM,gBAAgB,EAAE;AAC7C,UAAM,aAAa,MAAM,GAAG,QAAQ,OAAO;AAAA,MACvC,MAAM;AAAA,QACF,OAAO,sBAAsB,KAAK;AAAA,QAClC,OAAO;AAAA,MACX;AAAA,MACA,SAAS;AAAA,IACb,CAAC;AAED,QAAI,UAAU,gBAAgB,MAAM;AAChC,YAAM,GAAG,cAAc,OAAO;AAAA,QAC1B,OAAO,EAAE,IAAI,kBAAkB;AAAA,QAC/B,MAAM,EAAE,aAAa,WAAW,GAAG;AAAA,MACvC,CAAC;AAAA,IACL;AAEA,WAAO,iBAAiB,UAAU;AAAA,EACtC,CAAC;AACL;AAEO,MAAM,gBAAgB,OAAO,IAAY,UAAkB;AAC9D,QAAM,aAAa,MAAM,OAAO,QAAQ,OAAO;AAAA,IAC3C,OAAO,EAAE,IAAI,YAAY,EAAE,EAAE;AAAA,IAC7B,MAAM;AAAA,MACF,OAAO,sBAAsB,KAAK;AAAA,IACtC;AAAA,IACA,SAAS;AAAA,EACb,CAAC;AAED,SAAO,iBAAiB,UAAU;AACtC;AAEO,MAAM,gBAAgB,OAAO,OAAe;AAC/C,QAAM,QAAQ,YAAY,EAAE;AAE5B,SAAO,OAAO,aAAa,OAAO,OAAO;AACrC,UAAM,YAAY,MAAM,oBAAoB,EAAE;AAC9C,UAAM,OAAO,MAAM,GAAG,QAAQ,SAAS;AAAA,MACnC,SAAS,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,MAChD,QAAQ,EAAE,IAAI,KAAK;AAAA,IACvB,CAAC;AAED,QAAI,CAAC,KAAK,KAAK,CAAC,QAAQ,IAAI,OAAO,KAAK,GAAG;AACvC,aAAO;AAAA,IACX;AAEA,UAAM,GAAG,QAAQ,OAAO,EAAE,OAAO,EAAE,IAAI,MAAM,EAAE,CAAC;AAChD,UAAM,mBAAmB,EAAE;AAE3B,UAAM,kBAAkB;AAAA,MACpB,KAAK,IAAI,CAAC,QAAQ,IAAI,EAAE;AAAA,MACxB;AAAA,MACA,UAAU;AAAA,IACd;AAEA,UAAM,GAAG,cAAc,OAAO;AAAA,MAC1B,OAAO,EAAE,IAAI,kBAAkB;AAAA,MAC/B,MAAM,EAAE,aAAa,gBAAgB;AAAA,IACzC,CAAC;AAED,WAAO;AAAA,EACX,CAAC;AACL;AAEO,MAAM,mBAAmB,OAAO,OAAe;AAClD,QAAM,QAAQ,YAAY,EAAE;AAE5B,SAAO,OAAO,aAAa,OAAO,OAAO;AACrC,UAAM,YAAY,MAAM,oBAAoB,EAAE;AAC9C,UAAM,MAAM,MAAM,GAAG,QAAQ,WAAW;AAAA,MACpC,OAAO,EAAE,IAAI,MAAM;AAAA,MACnB,QAAQ,EAAE,IAAI,KAAK;AAAA,IACvB,CAAC;AAED,QAAI,CAAC,KAAK;AACN,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACzC;AAEA,UAAM,GAAG,cAAc,OAAO;AAAA,MAC1B,OAAO,EAAE,IAAI,UAAU,GAAG;AAAA,MAC1B,MAAM,EAAE,aAAa,MAAM;AAAA,IAC/B,CAAC;AAED,WAAO,kBAAkB,EAAE;AAAA,EAC/B,CAAC;AACL;AAEO,MAAM,kBAAkB,OAAO,WAAqB;AACvD,QAAM,aAAa,OAAO,IAAI,WAAW;AAEzC,SAAO,OAAO,aAAa,OAAO,OAAO;AACrC,UAAM,OAAO,MAAM,GAAG,QAAQ,SAAS;AAAA,MACnC,SAAS,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,MAChD,QAAQ,EAAE,IAAI,KAAK;AAAA,IACvB,CAAC;AACD,UAAM,gBAAgB,KAAK,IAAI,CAAC,QAAQ,IAAI,EAAE;AAE9C,0BAAsB,eAAe,UAAU;AAE/C,UAAM,QAAQ;AAAA,MACV,WAAW;AAAA,QAAI,CAAC,OAAO,UACnB,GAAG,QAAQ,OAAO;AAAA,UACd,OAAO,EAAE,IAAI,MAAM;AAAA,UACnB,MAAM,EAAE,OAAO,MAAM;AAAA,QACzB,CAAC;AAAA,MACL;AAAA,IACJ;AAEA,UAAM,gBAAgB,MAAM,mBAAmB,EAAE;AACjD,WAAO,cAAc,IAAI,gBAAgB;AAAA,EAC7C,CAAC;AACL;AAEO,MAAM,oBAAoB,OAAO,OAAe,UAA4B;AAC/E,QAAM,eAAe,YAAY,KAAK;AACtC,QAAM,cAAc,0BAA0B,KAAK;AAEnD,SAAO,OAAO,aAAa,OAAO,OAAO;AACrC,UAAM,MAAM,MAAM,GAAG,QAAQ,WAAW;AAAA,MACpC,OAAO,EAAE,IAAI,aAAa;AAAA,MAC1B,QAAQ,EAAE,IAAI,KAAK;AAAA,IACvB,CAAC;AAED,QAAI,CAAC,KAAK;AACN,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACzC;AAEA,UAAM,mBAAmB,MAAM,oBAAoB,IAAI,YAAY;AAEnE,UAAM,iBAAiB,MAAM,GAAG,YAAY,OAAO;AAAA,MAC/C,MAAM;AAAA,QACF,OAAO;AAAA,QACP,OAAO,YAAY;AAAA,QACnB,MAAM,YAAY;AAAA,QAClB,OAAO,YAAY;AAAA,QACnB,OAAO;AAAA,QACP,MAAM;AAAA,UACF,QAAQ,YAAY,SAAS,IAAI,CAAC,SAAS,WAAW;AAAA,YAClD,MAAM;AAAA,YACN,OAAO;AAAA,UACX,EAAE;AAAA,QACN;AAAA,MACJ;AAAA,MACA,SAAS;AAAA,IACb,CAAC;AAED,WAAO,qBAAqB,cAAc;AAAA,EAC9C,CAAC;AACL;AAEO,MAAM,oBAAoB,OAAO,IAAY,UAA4B;AAC5E,QAAM,YAAY,YAAY,EAAE;AAChC,QAAM,cAAc,0BAA0B,KAAK;AAEnD,QAAM,iBAAiB,MAAM,OAAO,YAAY,OAAO;AAAA,IACnD,OAAO,EAAE,IAAI,UAAU;AAAA,IACvB,MAAM;AAAA,MACF,OAAO,YAAY;AAAA,MACnB,MAAM,YAAY;AAAA,MAClB,OAAO,YAAY;AAAA,MACnB,MAAM;AAAA,QACF,YAAY,CAAC;AAAA,QACb,QAAQ,YAAY,SAAS,IAAI,CAAC,SAAS,WAAW;AAAA,UAClD,MAAM;AAAA,UACN,OAAO;AAAA,QACX,EAAE;AAAA,MACN;AAAA,IACJ;AAAA,IACA,SAAS;AAAA,EACb,CAAC;AAED,SAAO,qBAAqB,cAAc;AAC9C;AAEO,MAAM,oBAAoB,OAAO,OAAe;AACnD,QAAM,YAAY,YAAY,EAAE;AAEhC,SAAO,OAAO,aAAa,OAAO,OAAO;AACrC,UAAM,UAAU,MAAM,GAAG,YAAY,WAAW;AAAA,MAC5C,OAAO,EAAE,IAAI,UAAU;AAAA,MACvB,QAAQ,EAAE,IAAI,MAAM,OAAO,KAAK;AAAA,IACpC,CAAC;AAED,QAAI,CAAC,SAAS;AACV,aAAO;AAAA,IACX;AAEA,UAAM,GAAG,YAAY,OAAO;AAAA,MACxB,OAAO,EAAE,IAAI,UAAU;AAAA,IAC3B,CAAC;AACD,UAAM,uBAAuB,IAAI,QAAQ,KAAK;AAE9C,WAAO;AAAA,EACX,CAAC;AACL;AAEO,MAAM,sBAAsB,OAAO,OAAe,eAAyB;AAC9E,QAAM,eAAe,YAAY,KAAK;AACtC,QAAM,iBAAiB,WAAW,IAAI,WAAW;AAEjD,SAAO,OAAO,aAAa,OAAO,OAAO;AACrC,UAAM,WAAW,MAAM,GAAG,YAAY,SAAS;AAAA,MAC3C,OAAO,EAAE,OAAO,aAAa;AAAA,MAC7B,SAAS,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,MAChD,QAAQ,EAAE,IAAI,KAAK;AAAA,IACvB,CAAC;AACD,UAAM,oBAAoB,SAAS,IAAI,CAAC,YAAY,QAAQ,EAAE;AAE9D,0BAAsB,mBAAmB,cAAc;AAEvD,UAAM,QAAQ;AAAA,MACV,eAAe;AAAA,QAAI,CAAC,WAAW,UAC3B,GAAG,YAAY,OAAO;AAAA,UAClB,OAAO,EAAE,IAAI,UAAU;AAAA,UACvB,MAAM,EAAE,OAAO,MAAM;AAAA,QACzB,CAAC;AAAA,MACL;AAAA,IACJ;AAEA,UAAM,oBAAoB,MAAM,GAAG,YAAY,SAAS;AAAA,MACpD,OAAO,EAAE,OAAO,aAAa;AAAA,MAC7B,SAAS,CAAC,EAAE,OAAO,MAAM,GAAG,EAAE,WAAW,MAAM,CAAC;AAAA,MAChD,SAAS;AAAA,IACb,CAAC;AAED,WAAO,kBAAkB,IAAI,oBAAoB;AAAA,EACrD,CAAC;AACL;","names":[]}
|
package/server/dist/main.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createApp } from "./app.js";
|
|
2
2
|
import { logAuthConfig, resolveAuthConfig } from "./modules/auth-mode.js";
|
|
3
|
+
import { startDataMaintenanceScheduler } from "./modules/data-maintenance.js";
|
|
3
4
|
const PORT = Number(process.env.PORT || 6683);
|
|
4
5
|
const HOST = process.env.HOST || "0.0.0.0";
|
|
5
6
|
try {
|
|
@@ -9,6 +10,19 @@ try {
|
|
|
9
10
|
app.listen(PORT, HOST, () => {
|
|
10
11
|
process.stdout.write(`http server listen on ${HOST}:${PORT} (auth: ${authConfig.mode})
|
|
11
12
|
`);
|
|
13
|
+
startDataMaintenanceScheduler({
|
|
14
|
+
onResults: (results) => {
|
|
15
|
+
for (const result of results) {
|
|
16
|
+
process.stdout.write(`[maintenance] Reconciled ${result.processedCount} rows for ${result.key}
|
|
17
|
+
`);
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
onError: (error) => {
|
|
21
|
+
const message = error instanceof Error ? error.message : "Unknown data maintenance error";
|
|
22
|
+
process.stderr.write(`[maintenance] Background run failed: ${message}
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
12
26
|
});
|
|
13
27
|
} catch (error) {
|
|
14
28
|
const message = error instanceof Error ? error.message : "Unknown auth configuration error";
|
package/server/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/main.ts"],"sourcesContent":["import { createApp } from './app.js';\nimport { logAuthConfig, resolveAuthConfig } from './modules/auth-mode.js';\n\nconst PORT = Number(process.env.PORT || 6683);\nconst HOST = process.env.HOST || '0.0.0.0';\n\ntry {\n const authConfig = resolveAuthConfig(process.env);\n\n logAuthConfig(authConfig);\n\n const app = createApp(authConfig);\n\n app.listen(PORT, HOST, () => {\n process.stdout.write(`http server listen on ${HOST}:${PORT} (auth: ${authConfig.mode})\\n`);\n });\n} catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown auth configuration error';\n process.stderr.write(`[auth] Startup failed: ${message}\\n`);\n process.exit(1);\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAC1B,SAAS,eAAe,yBAAyB;
|
|
1
|
+
{"version":3,"sources":["../src/main.ts"],"sourcesContent":["import { createApp } from './app.js';\nimport { logAuthConfig, resolveAuthConfig } from './modules/auth-mode.js';\nimport { startDataMaintenanceScheduler } from './modules/data-maintenance.js';\n\nconst PORT = Number(process.env.PORT || 6683);\nconst HOST = process.env.HOST || '0.0.0.0';\n\ntry {\n const authConfig = resolveAuthConfig(process.env);\n\n logAuthConfig(authConfig);\n\n const app = createApp(authConfig);\n\n app.listen(PORT, HOST, () => {\n process.stdout.write(`http server listen on ${HOST}:${PORT} (auth: ${authConfig.mode})\\n`);\n\n startDataMaintenanceScheduler({\n onResults: (results) => {\n for (const result of results) {\n process.stdout.write(`[maintenance] Reconciled ${result.processedCount} rows for ${result.key}\\n`);\n }\n },\n onError: (error) => {\n const message = error instanceof Error ? error.message : 'Unknown data maintenance error';\n process.stderr.write(`[maintenance] Background run failed: ${message}\\n`);\n },\n });\n });\n} catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown auth configuration error';\n process.stderr.write(`[auth] Startup failed: ${message}\\n`);\n process.exit(1);\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAC1B,SAAS,eAAe,yBAAyB;AACjD,SAAS,qCAAqC;AAE9C,MAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,IAAI;AAC5C,MAAM,OAAO,QAAQ,IAAI,QAAQ;AAEjC,IAAI;AACA,QAAM,aAAa,kBAAkB,QAAQ,GAAG;AAEhD,gBAAc,UAAU;AAExB,QAAM,MAAM,UAAU,UAAU;AAEhC,MAAI,OAAO,MAAM,MAAM,MAAM;AACzB,YAAQ,OAAO,MAAM,yBAAyB,IAAI,IAAI,IAAI,WAAW,WAAW,IAAI;AAAA,CAAK;AAEzF,kCAA8B;AAAA,MAC1B,WAAW,CAAC,YAAY;AACpB,mBAAW,UAAU,SAAS;AAC1B,kBAAQ,OAAO,MAAM,4BAA4B,OAAO,cAAc,aAAa,OAAO,GAAG;AAAA,CAAI;AAAA,QACrG;AAAA,MACJ;AAAA,MACA,SAAS,CAAC,UAAU;AAChB,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,gBAAQ,OAAO,MAAM,wCAAwC,OAAO;AAAA,CAAI;AAAA,MAC5E;AAAA,IACJ,CAAC;AAAA,EACL,CAAC;AACL,SAAS,OAAO;AACZ,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,UAAQ,OAAO,MAAM,0BAA0B,OAAO;AAAA,CAAI;AAC1D,UAAQ,KAAK,CAAC;AAClB;","names":[]}
|
|
@@ -1,35 +1,28 @@
|
|
|
1
|
+
import { buildUnauthorizedGraphqlPayload, buildUnauthorizedPayload } from "@baejino/auth";
|
|
1
2
|
import session from "express-session";
|
|
2
3
|
import { GraphQLError } from "graphql";
|
|
3
4
|
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
4
|
-
const buildUnauthorizedPayload = () => ({
|
|
5
|
-
code: "UNAUTHORIZED",
|
|
6
|
-
message: "Authentication required"
|
|
7
|
-
});
|
|
8
|
-
const buildUnauthorizedGraphqlPayload = () => ({
|
|
9
|
-
errors: [{
|
|
10
|
-
message: "Authentication required",
|
|
11
|
-
extensions: { code: "UNAUTHORIZED" }
|
|
12
|
-
}]
|
|
13
|
-
});
|
|
14
5
|
const isAuthenticatedRequest = (req) => Boolean(req.session?.authenticated);
|
|
15
6
|
const createSessionMiddleware = (authConfig) => {
|
|
16
|
-
if (authConfig.mode !== "password"
|
|
7
|
+
if (authConfig.mode !== "password") {
|
|
17
8
|
return (_req, _res, next) => next();
|
|
18
9
|
}
|
|
19
10
|
return session({
|
|
20
11
|
secret: authConfig.sessionSecret,
|
|
21
|
-
name:
|
|
12
|
+
name: authConfig.cookieName,
|
|
22
13
|
resave: false,
|
|
23
14
|
saveUninitialized: false,
|
|
24
15
|
cookie: {
|
|
25
16
|
httpOnly: true,
|
|
26
|
-
sameSite: "lax"
|
|
17
|
+
sameSite: "lax",
|
|
18
|
+
secure: process.env.NODE_ENV === "production",
|
|
19
|
+
path: "/"
|
|
27
20
|
}
|
|
28
21
|
});
|
|
29
22
|
};
|
|
30
23
|
const requireSessionForWrite = (authConfig) => {
|
|
31
24
|
return (req, res, next) => {
|
|
32
|
-
if (authConfig.mode === "
|
|
25
|
+
if (authConfig.mode === "open" || isAuthenticatedRequest(req)) {
|
|
33
26
|
next();
|
|
34
27
|
return;
|
|
35
28
|
}
|
|
@@ -38,7 +31,7 @@ const requireSessionForWrite = (authConfig) => {
|
|
|
38
31
|
};
|
|
39
32
|
const requireSessionForGraphql = (authConfig) => {
|
|
40
33
|
return (req, res, next) => {
|
|
41
|
-
if (authConfig.mode === "
|
|
34
|
+
if (authConfig.mode === "open" || isAuthenticatedRequest(req)) {
|
|
42
35
|
next();
|
|
43
36
|
return;
|
|
44
37
|
}
|
|
@@ -52,13 +45,15 @@ const createMutationAuthValidationRule = () => {
|
|
|
52
45
|
if (node.operation !== "mutation") {
|
|
53
46
|
return;
|
|
54
47
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
{
|
|
48
|
+
const unauthorizedError = buildUnauthorizedGraphqlPayload().errors[0];
|
|
49
|
+
context.reportError(
|
|
50
|
+
new GraphQLError(unauthorizedError.message, {
|
|
58
51
|
nodes: [node],
|
|
59
|
-
extensions: {
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
extensions: {
|
|
53
|
+
code: unauthorizedError.extensions.code
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
);
|
|
62
57
|
}
|
|
63
58
|
};
|
|
64
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/auth-guard.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/modules/auth-guard.ts"],"sourcesContent":["import { buildUnauthorizedGraphqlPayload, buildUnauthorizedPayload } from '@baejino/auth';\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport session from 'express-session';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport type { AuthConfig } from './auth-mode.js';\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nexport const isAuthenticatedRequest = (req: Request) => Boolean(req.session?.authenticated);\n\nexport const createSessionMiddleware = (authConfig: AuthConfig): RequestHandler => {\n if (authConfig.mode !== 'password') {\n return (_req, _res, next) => next();\n }\n\n return session({\n secret: authConfig.sessionSecret,\n name: authConfig.cookieName,\n resave: false,\n saveUninitialized: false,\n cookie: {\n httpOnly: true,\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n },\n });\n};\n\nexport const requireSessionForWrite = (authConfig: AuthConfig): RequestHandler => {\n return (req: Request, res: Response, next: NextFunction) => {\n if (authConfig.mode === 'open' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res.status(401).set(JSON_HEADERS).json(buildUnauthorizedPayload()).end();\n };\n};\n\nexport const requireSessionForGraphql = (authConfig: AuthConfig): RequestHandler => {\n return (req: Request, res: Response, next: NextFunction) => {\n if (authConfig.mode === 'open' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res.status(401).set(JSON_HEADERS).json(buildUnauthorizedGraphqlPayload()).end();\n };\n};\n\nexport const createMutationAuthValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation !== 'mutation') {\n return;\n }\n\n const unauthorizedError = buildUnauthorizedGraphqlPayload().errors[0];\n\n context.reportError(\n new GraphQLError(unauthorizedError.message, {\n nodes: [node],\n extensions: {\n code: unauthorizedError.extensions.code,\n },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"AAAA,SAAS,iCAAiC,gCAAgC;AAE1E,OAAO,aAAa;AAEpB,SAAS,oBAAoB;AAI7B,MAAM,eAAe,EAAE,gBAAgB,mBAAmB;AAEnD,MAAM,yBAAyB,CAAC,QAAiB,QAAQ,IAAI,SAAS,aAAa;AAEnF,MAAM,0BAA0B,CAAC,eAA2C;AAC/E,MAAI,WAAW,SAAS,YAAY;AAChC,WAAO,CAAC,MAAM,MAAM,SAAS,KAAK;AAAA,EACtC;AAEA,SAAO,QAAQ;AAAA,IACX,QAAQ,WAAW;AAAA,IACnB,MAAM,WAAW;AAAA,IACjB,QAAQ;AAAA,IACR,mBAAmB;AAAA,IACnB,QAAQ;AAAA,MACJ,UAAU;AAAA,MACV,UAAU;AAAA,MACV,QAAQ,QAAQ,IAAI,aAAa;AAAA,MACjC,MAAM;AAAA,IACV;AAAA,EACJ,CAAC;AACL;AAEO,MAAM,yBAAyB,CAAC,eAA2C;AAC9E,SAAO,CAAC,KAAc,KAAe,SAAuB;AACxD,QAAI,WAAW,SAAS,UAAU,uBAAuB,GAAG,GAAG;AAC3D,WAAK;AACL;AAAA,IACJ;AAEA,QAAI,OAAO,GAAG,EAAE,IAAI,YAAY,EAAE,KAAK,yBAAyB,CAAC,EAAE,IAAI;AAAA,EAC3E;AACJ;AAEO,MAAM,2BAA2B,CAAC,eAA2C;AAChF,SAAO,CAAC,KAAc,KAAe,SAAuB;AACxD,QAAI,WAAW,SAAS,UAAU,uBAAuB,GAAG,GAAG;AAC3D,WAAK;AACL;AAAA,IACJ;AAEA,QAAI,OAAO,GAAG,EAAE,IAAI,YAAY,EAAE,KAAK,gCAAgC,CAAC,EAAE,IAAI;AAAA,EAClF;AACJ;AAEO,MAAM,mCAAmC,MAAsB;AAClE,SAAO,CAAC,YAAY;AAChB,WAAO;AAAA,MACH,oBAAoB,MAAM;AACtB,YAAI,KAAK,cAAc,YAAY;AAC/B;AAAA,QACJ;AAEA,cAAM,oBAAoB,gCAAgC,EAAE,OAAO,CAAC;AAEpE,gBAAQ;AAAA,UACJ,IAAI,aAAa,kBAAkB,SAAS;AAAA,YACxC,OAAO,CAAC,IAAI;AAAA,YACZ,YAAY;AAAA,cACR,MAAM,kBAAkB,WAAW;AAAA,YACvC;AAAA,UACJ,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACJ;","names":[]}
|