@agent-native/core 0.102.2 → 0.103.0
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/docs/content/local-file-mode.mdx +99 -489
- package/corpus/core/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/corpus/core/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/corpus/core/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/corpus/core/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/corpus/core/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/corpus/core/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/corpus/core/docs/content/template-content.mdx +26 -42
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/content-local.ts +86 -18
- package/corpus/core/src/cli/index.ts +2 -2
- package/corpus/core/src/cli/skills-content/content-skill.ts +36 -25
- package/corpus/core/src/cli/skills.ts +47 -13
- package/corpus/core/src/local-artifacts/index.ts +65 -0
- package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +325 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +12 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/assets/app/i18n-data.ts +215 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +19 -479
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +446 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +12 -2
- package/corpus/templates/clips/app/components/player/settings-panel.tsx +30 -12
- package/corpus/templates/clips/app/components/player/video-player.tsx +5 -1
- package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +22 -3
- package/corpus/templates/clips/app/i18n/ar-SA.ts +3 -2
- package/corpus/templates/clips/app/i18n/de-DE.ts +4 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -2
- package/corpus/templates/clips/app/i18n/es-ES.ts +4 -2
- package/corpus/templates/clips/app/i18n/fr-FR.ts +3 -2
- package/corpus/templates/clips/app/i18n/hi-IN.ts +3 -2
- package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +3 -2
- package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -2
- package/corpus/templates/clips/app/i18n/zh-CN.ts +3 -2
- package/corpus/templates/clips/app/i18n/zh-TW.ts +3 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +27 -1
- package/corpus/templates/clips/server/lib/share-password.ts +6 -3
- package/corpus/templates/content/.agents/skills/content/SKILL.md +26 -39
- package/corpus/templates/content/AGENTS.md +25 -30
- package/corpus/templates/content/actions/_builder-docs-client.ts +23 -0
- package/corpus/templates/content/actions/_content-database-source-adapters.ts +21 -3
- package/corpus/templates/content/actions/_content-files.ts +338 -0
- package/corpus/templates/content/actions/_content-space-access.ts +135 -0
- package/corpus/templates/content/actions/_content-spaces.ts +640 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +90 -7
- package/corpus/templates/content/actions/_database-utils.ts +7 -1
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -9
- package/corpus/templates/content/actions/_local-folder-source.ts +53 -0
- package/corpus/templates/content/actions/_property-utils.ts +9 -4
- package/corpus/templates/content/actions/add-database-item.ts +108 -65
- package/corpus/templates/content/actions/backfill-content-files.ts +22 -0
- package/corpus/templates/content/actions/connect-local-folder-source.ts +221 -0
- package/corpus/templates/content/actions/create-content-database.ts +129 -3
- package/corpus/templates/content/actions/create-document.ts +65 -55
- package/corpus/templates/content/actions/create-inline-content-database.ts +6 -1
- package/corpus/templates/content/actions/delete-content-database.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +7 -11
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +175 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +10 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +13 -0
- package/corpus/templates/content/actions/edit-document.ts +2 -28
- package/corpus/templates/content/actions/ensure-content-spaces.ts +26 -0
- package/corpus/templates/content/actions/export-content-source.ts +47 -9
- package/corpus/templates/content/actions/get-document.ts +0 -14
- package/corpus/templates/content/actions/import-content-source.ts +43 -3
- package/corpus/templates/content/actions/list-content-spaces.ts +103 -0
- package/corpus/templates/content/actions/list-documents.ts +12 -9
- package/corpus/templates/content/actions/move-document.ts +3 -14
- package/corpus/templates/content/actions/pull-document.ts +0 -23
- package/corpus/templates/content/actions/remove-local-file-source.ts +50 -29
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +442 -0
- package/corpus/templates/content/actions/reveal-local-source-file.ts +6 -17
- package/corpus/templates/content/actions/search-documents.ts +0 -34
- package/corpus/templates/content/actions/share-local-file-document.ts +25 -1
- package/corpus/templates/content/actions/submit-content-database-form.ts +12 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +731 -0
- package/corpus/templates/content/actions/sync-manifest-local-folder-source.ts +123 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +10 -1
- package/corpus/templates/content/actions/update-document.ts +0 -15
- package/corpus/templates/content/actions/view-screen.ts +2 -50
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +76 -62
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +70 -22
- package/corpus/templates/content/app/components/editor/database/grouping.ts +2 -1
- package/corpus/templates/content/app/components/editor/database/settings.tsx +23 -20
- package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +330 -0
- package/corpus/templates/content/app/components/editor/database/types.ts +1 -0
- package/corpus/templates/content/app/components/editor/database/view-config.ts +3 -1
- package/corpus/templates/content/app/components/layout/Layout.tsx +25 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +209 -113
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -0
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +39 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +11 -0
- package/corpus/templates/content/app/i18n-data.ts +75 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +174 -65
- package/corpus/templates/content/changelog/2026-07-16-content-callouts-can-now-be-reviewed-and-published-to-builder.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-heading-5-and-heading-6-are-available-from-the-editor-slash-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-organize-every-workspace-through-a-customizable-files-databa.md +6 -0
- package/corpus/templates/content/parity/matrix.md +2 -1
- package/corpus/templates/content/parity/matrix.ts +37 -1
- package/corpus/templates/content/scripts/dev-database.mjs +7 -5
- package/corpus/templates/content/server/db/schema.ts +113 -40
- package/corpus/templates/content/server/lib/document-context.ts +15 -1
- package/corpus/templates/content/server/lib/notion-sync.ts +6 -0
- package/corpus/templates/content/server/plugins/db.ts +153 -0
- package/corpus/templates/content/shared/api.ts +17 -3
- package/corpus/templates/content/shared/builder-mdx.ts +163 -16
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +59 -0
- package/corpus/templates/design/AGENTS.md +6 -1
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -7
- package/corpus/templates/design/actions/list-design-templates.ts +69 -21
- package/corpus/templates/design/actions/navigate.ts +2 -2
- package/corpus/templates/design/actions/view-screen.ts +15 -2
- package/corpus/templates/design/agent-native.app-skill.json +5 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +281 -56
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +14 -2
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -3
- package/corpus/templates/design/app/i18n-data.ts +21 -2
- package/corpus/templates/design/app/i18n-template-feature.ts +218 -10
- package/corpus/templates/design/app/pages/Index.tsx +195 -24
- package/corpus/templates/design/app/pages/Templates.tsx +144 -55
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +12 -1
- package/corpus/templates/design/changelog/2026-07-14-templates-now-appear-in-the-new-design-picker-with-previews-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-templates-now-open-immediately-after-copying-even-while-the-.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/mail/AGENTS.md +23 -18
- package/corpus/templates/mail/actions/bulk-archive.ts +32 -28
- package/corpus/templates/mail/actions/mark-read.ts +131 -20
- package/corpus/templates/mail/actions/mark-thread-read.ts +1 -1
- package/corpus/templates/mail/actions/move-email.ts +30 -24
- package/corpus/templates/mail/actions/send-email.ts +73 -68
- package/corpus/templates/mail/changelog/2026-07-15-unread-inbox-cleanup-now-completes-reliably.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +218 -209
- package/corpus/templates/mail/server/lib/bulk-mark-read.ts +30 -0
- package/corpus/templates/mail/server/lib/email-state.ts +198 -113
- package/corpus/templates/mail/server/lib/google-auth.ts +218 -25
- package/corpus/templates/mail/server/lib/jobs.ts +69 -74
- package/corpus/templates/mail/server/lib/local-email-store.ts +265 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
- package/dist/cli/content-local.d.ts +1 -0
- package/dist/cli/content-local.d.ts.map +1 -1
- package/dist/cli/content-local.js +75 -17
- package/dist/cli/content-local.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills-content/content-skill.d.ts +1 -1
- package/dist/cli/skills-content/content-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/content-skill.js +36 -25
- package/dist/cli/skills-content/content-skill.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +47 -14
- package/dist/cli/skills.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/local-artifacts/index.d.ts +10 -0
- package/dist/local-artifacts/index.d.ts.map +1 -1
- package/dist/local-artifacts/index.js +39 -0
- package/dist/local-artifacts/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/docs/content/local-file-mode.mdx +99 -489
- package/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/docs/content/template-content.mdx +26 -42
- package/package.json +1 -1
- package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +0 -126
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { writeAppState } from "@agent-native/core/application-state";
|
|
3
|
+
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
4
|
+
import { and, eq } from "drizzle-orm";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
8
|
+
import type { ContentDatabaseSourceTruthPolicy } from "../shared/api.js";
|
|
9
|
+
import {
|
|
10
|
+
normalizeContentSpaceEmail,
|
|
11
|
+
resolveContentSpaceAccess,
|
|
12
|
+
} from "./_content-space-access.js";
|
|
13
|
+
import {
|
|
14
|
+
personalContentSpaceId,
|
|
15
|
+
provisionContentSpaces,
|
|
16
|
+
provisionSourceBackedContentSpace,
|
|
17
|
+
} from "./_content-spaces.js";
|
|
18
|
+
import {
|
|
19
|
+
LOCAL_FOLDER_SOURCE_TYPE,
|
|
20
|
+
localFolderSourceCapabilities,
|
|
21
|
+
localFolderSourceId,
|
|
22
|
+
localFolderSourceMetadata,
|
|
23
|
+
} from "./_local-folder-source.js";
|
|
24
|
+
|
|
25
|
+
const truthPolicySchema = z
|
|
26
|
+
.enum(["database_primary", "source_primary", "reviewed_bidirectional"])
|
|
27
|
+
.default("database_primary");
|
|
28
|
+
|
|
29
|
+
export default defineAction({
|
|
30
|
+
description:
|
|
31
|
+
"Connect an opaque trusted local-folder handle to a canonical Files database. The browser or Desktop bridge keeps the real handle/path; Content stores only safe connection metadata.",
|
|
32
|
+
schema: z.object({
|
|
33
|
+
connectionId: z
|
|
34
|
+
.string()
|
|
35
|
+
.min(1)
|
|
36
|
+
.max(300)
|
|
37
|
+
.describe("Stable opaque ID from the trusted browser/Desktop registry"),
|
|
38
|
+
label: z.string().min(1).max(200).describe("User-visible folder label"),
|
|
39
|
+
spaceId: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe(
|
|
43
|
+
"Existing Content space whose Files database receives the source",
|
|
44
|
+
),
|
|
45
|
+
databaseId: z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Existing canonical Files database receiving the source"),
|
|
49
|
+
createSourceBackedSpace: z
|
|
50
|
+
.boolean()
|
|
51
|
+
.optional()
|
|
52
|
+
.default(false)
|
|
53
|
+
.describe("Create a separate private Content space for this folder"),
|
|
54
|
+
truthPolicy: truthPolicySchema.describe(
|
|
55
|
+
"Whether Content, the folder, or reviewed conflict resolution is authoritative",
|
|
56
|
+
),
|
|
57
|
+
dryRun: z.boolean().optional().default(false),
|
|
58
|
+
}),
|
|
59
|
+
run: async (args) => {
|
|
60
|
+
const userEmail = getRequestUserEmail();
|
|
61
|
+
if (!userEmail) throw new Error("no authenticated user");
|
|
62
|
+
if (args.spaceId && args.databaseId) {
|
|
63
|
+
throw new Error("Choose either spaceId or databaseId, not both");
|
|
64
|
+
}
|
|
65
|
+
if (args.createSourceBackedSpace && (args.spaceId || args.databaseId)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"A new source-backed space cannot also target an existing space or database",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const db = getDb();
|
|
72
|
+
const connectionId = args.connectionId.trim();
|
|
73
|
+
const label = args.label.trim();
|
|
74
|
+
const truthPolicy = args.truthPolicy as ContentDatabaseSourceTruthPolicy;
|
|
75
|
+
if (args.dryRun) {
|
|
76
|
+
const [existing] = await db
|
|
77
|
+
.select({
|
|
78
|
+
source: schema.contentDatabaseSources,
|
|
79
|
+
database: schema.contentDatabases,
|
|
80
|
+
})
|
|
81
|
+
.from(schema.contentDatabaseSources)
|
|
82
|
+
.innerJoin(
|
|
83
|
+
schema.contentDatabases,
|
|
84
|
+
eq(
|
|
85
|
+
schema.contentDatabases.id,
|
|
86
|
+
schema.contentDatabaseSources.databaseId,
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
.where(
|
|
90
|
+
and(
|
|
91
|
+
eq(
|
|
92
|
+
schema.contentDatabaseSources.sourceType,
|
|
93
|
+
LOCAL_FOLDER_SOURCE_TYPE,
|
|
94
|
+
),
|
|
95
|
+
eq(schema.contentDatabaseSources.sourceTable, connectionId),
|
|
96
|
+
eq(
|
|
97
|
+
schema.contentDatabaseSources.ownerEmail,
|
|
98
|
+
normalizeContentSpaceEmail(userEmail),
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
if (!existing?.database.spaceId) {
|
|
103
|
+
return {
|
|
104
|
+
connected: false as const,
|
|
105
|
+
sourceId: null,
|
|
106
|
+
connectionId,
|
|
107
|
+
label,
|
|
108
|
+
truthPolicy,
|
|
109
|
+
spaceId: null,
|
|
110
|
+
filesDatabaseId: null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
await resolveContentSpaceAccess(existing.database.spaceId, "editor");
|
|
114
|
+
return {
|
|
115
|
+
connected: true as const,
|
|
116
|
+
sourceId: existing.source.id,
|
|
117
|
+
sourceType: LOCAL_FOLDER_SOURCE_TYPE,
|
|
118
|
+
connectionId,
|
|
119
|
+
label,
|
|
120
|
+
truthPolicy,
|
|
121
|
+
spaceId: existing.database.spaceId,
|
|
122
|
+
filesDatabaseId: existing.database.id,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
await provisionContentSpaces(db, userEmail);
|
|
126
|
+
|
|
127
|
+
let targetSpaceId = args.spaceId;
|
|
128
|
+
let targetDatabaseId = args.databaseId;
|
|
129
|
+
if (args.createSourceBackedSpace) {
|
|
130
|
+
const provisioned = await provisionSourceBackedContentSpace(
|
|
131
|
+
db,
|
|
132
|
+
userEmail,
|
|
133
|
+
{ connectionId, name: label },
|
|
134
|
+
);
|
|
135
|
+
targetSpaceId = provisioned.spaceId;
|
|
136
|
+
targetDatabaseId = provisioned.filesDatabaseId;
|
|
137
|
+
} else if (!targetSpaceId && !targetDatabaseId) {
|
|
138
|
+
targetSpaceId = personalContentSpaceId(userEmail);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let database: typeof schema.contentDatabases.$inferSelect | undefined;
|
|
142
|
+
if (targetDatabaseId) {
|
|
143
|
+
[database] = await db
|
|
144
|
+
.select()
|
|
145
|
+
.from(schema.contentDatabases)
|
|
146
|
+
.where(eq(schema.contentDatabases.id, targetDatabaseId));
|
|
147
|
+
if (!database || database.deletedAt || database.systemRole !== "files") {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Database "${targetDatabaseId}" is not a canonical Files database`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (!database.spaceId) {
|
|
153
|
+
throw new Error(`Database "${targetDatabaseId}" has no Content space`);
|
|
154
|
+
}
|
|
155
|
+
targetSpaceId = database.spaceId;
|
|
156
|
+
}
|
|
157
|
+
if (!targetSpaceId) throw new Error("Content space is required");
|
|
158
|
+
|
|
159
|
+
await resolveContentSpaceAccess(targetSpaceId, "editor");
|
|
160
|
+
if (!database) {
|
|
161
|
+
[database] = await db
|
|
162
|
+
.select()
|
|
163
|
+
.from(schema.contentDatabases)
|
|
164
|
+
.where(
|
|
165
|
+
and(
|
|
166
|
+
eq(schema.contentDatabases.spaceId, targetSpaceId),
|
|
167
|
+
eq(schema.contentDatabases.systemRole, "files"),
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (!database || database.deletedAt) {
|
|
172
|
+
throw new Error(`Content space "${targetSpaceId}" has no Files database`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const id = localFolderSourceId(database.id, connectionId);
|
|
176
|
+
const now = new Date().toISOString();
|
|
177
|
+
const values = {
|
|
178
|
+
id,
|
|
179
|
+
ownerEmail: database.ownerEmail,
|
|
180
|
+
orgId: database.orgId,
|
|
181
|
+
databaseId: database.id,
|
|
182
|
+
sourceType: LOCAL_FOLDER_SOURCE_TYPE,
|
|
183
|
+
sourceName: label,
|
|
184
|
+
sourceTable: connectionId,
|
|
185
|
+
syncState: "linked",
|
|
186
|
+
freshness: "unknown",
|
|
187
|
+
capabilitiesJson: JSON.stringify(localFolderSourceCapabilities()),
|
|
188
|
+
metadataJson: JSON.stringify(
|
|
189
|
+
localFolderSourceMetadata({ connectionId, label, truthPolicy }),
|
|
190
|
+
),
|
|
191
|
+
lastError: null,
|
|
192
|
+
createdAt: now,
|
|
193
|
+
updatedAt: now,
|
|
194
|
+
};
|
|
195
|
+
await db
|
|
196
|
+
.insert(schema.contentDatabaseSources)
|
|
197
|
+
.values(values)
|
|
198
|
+
.onConflictDoNothing();
|
|
199
|
+
await db
|
|
200
|
+
.update(schema.contentDatabaseSources)
|
|
201
|
+
.set({
|
|
202
|
+
sourceName: label,
|
|
203
|
+
capabilitiesJson: values.capabilitiesJson,
|
|
204
|
+
metadataJson: values.metadataJson,
|
|
205
|
+
updatedAt: now,
|
|
206
|
+
})
|
|
207
|
+
.where(eq(schema.contentDatabaseSources.id, id));
|
|
208
|
+
|
|
209
|
+
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
210
|
+
return {
|
|
211
|
+
connected: true as const,
|
|
212
|
+
sourceId: id,
|
|
213
|
+
sourceType: LOCAL_FOLDER_SOURCE_TYPE,
|
|
214
|
+
connectionId,
|
|
215
|
+
label,
|
|
216
|
+
truthPolicy,
|
|
217
|
+
spaceId: targetSpaceId,
|
|
218
|
+
filesDatabaseId: database.id,
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
});
|
|
@@ -14,6 +14,12 @@ import type {
|
|
|
14
14
|
ContentDatabaseResponse,
|
|
15
15
|
CreateDatabaseRequest,
|
|
16
16
|
} from "../shared/api.js";
|
|
17
|
+
import { ensureDocumentFilesMembership } from "./_content-files.js";
|
|
18
|
+
import { resolveContentSpaceAccess } from "./_content-space-access.js";
|
|
19
|
+
import {
|
|
20
|
+
organizationContentSpaceId,
|
|
21
|
+
provisionContentSpaces,
|
|
22
|
+
} from "./_content-spaces.js";
|
|
17
23
|
import { getContentDatabaseResponse } from "./_database-utils.js";
|
|
18
24
|
import { documentsPositionScope, withPositionLock } from "./_position-utils.js";
|
|
19
25
|
import { nanoid, seedDefaultBlocksField } from "./_property-utils.js";
|
|
@@ -23,6 +29,10 @@ const createContentDatabaseSchema = z.object({
|
|
|
23
29
|
.string()
|
|
24
30
|
.optional()
|
|
25
31
|
.describe("Existing document to convert into a database page"),
|
|
32
|
+
spaceId: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Content space for a new top-level database"),
|
|
26
36
|
parentId: z
|
|
27
37
|
.string()
|
|
28
38
|
.nullish()
|
|
@@ -73,13 +83,84 @@ export async function createContentDatabaseCore(
|
|
|
73
83
|
args: CreateDatabaseRequest,
|
|
74
84
|
options: { db?: any } = {},
|
|
75
85
|
): Promise<ContentDatabaseResponse> {
|
|
76
|
-
const
|
|
86
|
+
const db = options.db ?? getDb();
|
|
87
|
+
const resolvedSpaceId = await resolveContentDatabaseSpace(args, db);
|
|
88
|
+
let databaseId: string | null = null;
|
|
89
|
+
if (options.db) {
|
|
90
|
+
databaseId = await createContentDatabaseRecord(args, {
|
|
91
|
+
db,
|
|
92
|
+
spaceId: resolvedSpaceId,
|
|
93
|
+
});
|
|
94
|
+
} else {
|
|
95
|
+
await db.transaction(async (tx: any) => {
|
|
96
|
+
databaseId = await createContentDatabaseRecord(args, {
|
|
97
|
+
db: tx,
|
|
98
|
+
spaceId: resolvedSpaceId,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (!databaseId) throw new Error("Content database was not created");
|
|
77
103
|
return getContentDatabaseResponse(databaseId);
|
|
78
104
|
}
|
|
79
105
|
|
|
106
|
+
async function healLegacyDocumentSpace(db: any, resource: any) {
|
|
107
|
+
const provisioned = await provisionContentSpaces(db, resource.ownerEmail);
|
|
108
|
+
const spaceId = resource.orgId
|
|
109
|
+
? organizationContentSpaceId(resource.orgId)
|
|
110
|
+
: provisioned.personalSpaceId;
|
|
111
|
+
const [space] = await db
|
|
112
|
+
.select({ id: schema.contentSpaces.id })
|
|
113
|
+
.from(schema.contentSpaces)
|
|
114
|
+
.where(eq(schema.contentSpaces.id, spaceId));
|
|
115
|
+
if (!space) {
|
|
116
|
+
throw new Error(`Unable to resolve a Content space for "${resource.id}"`);
|
|
117
|
+
}
|
|
118
|
+
const now = new Date().toISOString();
|
|
119
|
+
await db
|
|
120
|
+
.update(schema.documents)
|
|
121
|
+
.set({ spaceId, updatedAt: now })
|
|
122
|
+
.where(eq(schema.documents.id, resource.id));
|
|
123
|
+
await ensureDocumentFilesMembership(db, resource.id, now);
|
|
124
|
+
return spaceId;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function resolveContentDatabaseSpace(
|
|
128
|
+
args: CreateDatabaseRequest,
|
|
129
|
+
db: any,
|
|
130
|
+
): Promise<string> {
|
|
131
|
+
if (args.documentId) {
|
|
132
|
+
const access = await assertAccess("document", args.documentId, "editor");
|
|
133
|
+
const spaceId =
|
|
134
|
+
(access.resource.spaceId as string | null) ??
|
|
135
|
+
(await healLegacyDocumentSpace(db, access.resource));
|
|
136
|
+
if (args.spaceId && args.spaceId !== spaceId) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
"A converted database must keep its document Content space",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return spaceId;
|
|
142
|
+
}
|
|
143
|
+
if (args.parentId) {
|
|
144
|
+
const access = await assertAccess("document", args.parentId, "editor");
|
|
145
|
+
const spaceId =
|
|
146
|
+
(access.resource.spaceId as string | null) ??
|
|
147
|
+
(await healLegacyDocumentSpace(db, access.resource));
|
|
148
|
+
if (args.spaceId && args.spaceId !== spaceId) {
|
|
149
|
+
throw new Error("Nested databases must use their parent Content space");
|
|
150
|
+
}
|
|
151
|
+
return spaceId;
|
|
152
|
+
}
|
|
153
|
+
const userEmail = getRequestUserEmail();
|
|
154
|
+
if (!userEmail) throw new Error("no authenticated user");
|
|
155
|
+
const provisioned = await provisionContentSpaces(db, userEmail);
|
|
156
|
+
const spaceId = args.spaceId ?? provisioned.personalSpaceId;
|
|
157
|
+
await resolveContentSpaceAccess(spaceId, "editor");
|
|
158
|
+
return spaceId;
|
|
159
|
+
}
|
|
160
|
+
|
|
80
161
|
export async function createContentDatabaseRecord(
|
|
81
162
|
args: CreateDatabaseRequest,
|
|
82
|
-
options: { db?: any } = {},
|
|
163
|
+
options: { db?: any; spaceId?: string } = {},
|
|
83
164
|
): Promise<string> {
|
|
84
165
|
const db = options.db ?? getDb();
|
|
85
166
|
const now = new Date().toISOString();
|
|
@@ -89,6 +170,7 @@ export async function createContentDatabaseRecord(
|
|
|
89
170
|
let ownerEmail = getRequestUserEmail();
|
|
90
171
|
if (!ownerEmail) throw new Error("no authenticated user");
|
|
91
172
|
let orgId = getRequestOrgId() ?? null;
|
|
173
|
+
let spaceId = options.spaceId ?? null;
|
|
92
174
|
let inheritedShares: Array<{
|
|
93
175
|
principalType: "user" | "org";
|
|
94
176
|
principalId: string;
|
|
@@ -100,13 +182,34 @@ export async function createContentDatabaseRecord(
|
|
|
100
182
|
const document = access.resource;
|
|
101
183
|
ownerEmail = document.ownerEmail as string;
|
|
102
184
|
orgId = (document.orgId as string | null) ?? null;
|
|
185
|
+
spaceId = (document.spaceId as string | null) ?? spaceId;
|
|
186
|
+
if (!spaceId) {
|
|
187
|
+
throw new Error(`Document "${documentId}" has no Content space`);
|
|
188
|
+
}
|
|
189
|
+
if (args.spaceId && args.spaceId !== spaceId) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
"A converted database must keep its document Content space",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
103
194
|
title = databaseTitleForPage(title, document.title);
|
|
104
195
|
|
|
105
196
|
const [existing] = await db
|
|
106
197
|
.select()
|
|
107
198
|
.from(schema.contentDatabases)
|
|
108
199
|
.where(eq(schema.contentDatabases.documentId, documentId));
|
|
109
|
-
if (existing)
|
|
200
|
+
if (existing) {
|
|
201
|
+
if (existing.spaceId !== spaceId) {
|
|
202
|
+
await db
|
|
203
|
+
.update(schema.contentDatabases)
|
|
204
|
+
.set({ spaceId, updatedAt: now })
|
|
205
|
+
.where(eq(schema.contentDatabases.id, existing.id));
|
|
206
|
+
}
|
|
207
|
+
await ensureDocumentFilesMembership(db, documentId, now, {
|
|
208
|
+
userEmail: getRequestUserEmail(),
|
|
209
|
+
orgId: orgId ?? undefined,
|
|
210
|
+
});
|
|
211
|
+
return existing.id;
|
|
212
|
+
}
|
|
110
213
|
|
|
111
214
|
if (title && title !== document.title && !document.title.trim()) {
|
|
112
215
|
await db
|
|
@@ -131,6 +234,13 @@ export async function createContentDatabaseRecord(
|
|
|
131
234
|
const parent = parentAccess.resource;
|
|
132
235
|
ownerEmail = parent.ownerEmail as string;
|
|
133
236
|
orgId = (parent.orgId as string | null) ?? null;
|
|
237
|
+
spaceId = (parent.spaceId as string | null) ?? spaceId;
|
|
238
|
+
if (!spaceId) {
|
|
239
|
+
throw new Error(`Parent document "${parentId}" has no Content space`);
|
|
240
|
+
}
|
|
241
|
+
if (args.spaceId && args.spaceId !== spaceId) {
|
|
242
|
+
throw new Error("Nested databases must use their parent Content space");
|
|
243
|
+
}
|
|
134
244
|
visibility = parent.visibility ?? "private";
|
|
135
245
|
hideFromSearch = parent.hideFromSearch ?? 0;
|
|
136
246
|
inheritedShares = await db
|
|
@@ -141,6 +251,16 @@ export async function createContentDatabaseRecord(
|
|
|
141
251
|
})
|
|
142
252
|
.from(schema.documentShares)
|
|
143
253
|
.where(eq(schema.documentShares.resourceId, parentId));
|
|
254
|
+
} else {
|
|
255
|
+
if (!spaceId) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
"A top-level database requires a resolved Content space",
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const spaceAccess = await resolveContentSpaceAccess(spaceId, "editor");
|
|
261
|
+
ownerEmail = getRequestUserEmail() ?? ownerEmail;
|
|
262
|
+
orgId = spaceAccess.space.orgId;
|
|
263
|
+
visibility = orgId ? "org" : "private";
|
|
144
264
|
}
|
|
145
265
|
|
|
146
266
|
documentId = nanoid();
|
|
@@ -168,6 +288,7 @@ export async function createContentDatabaseRecord(
|
|
|
168
288
|
|
|
169
289
|
await db.insert(schema.documents).values({
|
|
170
290
|
id: documentId!,
|
|
291
|
+
spaceId,
|
|
171
292
|
ownerEmail: resolvedOwnerEmail,
|
|
172
293
|
orgId,
|
|
173
294
|
parentId,
|
|
@@ -203,6 +324,7 @@ export async function createContentDatabaseRecord(
|
|
|
203
324
|
const databaseId = nanoid();
|
|
204
325
|
await db.insert(schema.contentDatabases).values({
|
|
205
326
|
id: databaseId,
|
|
327
|
+
spaceId,
|
|
206
328
|
ownerEmail,
|
|
207
329
|
orgId,
|
|
208
330
|
documentId,
|
|
@@ -214,6 +336,10 @@ export async function createContentDatabaseRecord(
|
|
|
214
336
|
// Every database is seeded with one primary "Content" Blocks field, backed
|
|
215
337
|
// by `documents.content`, so each row's body is a first-class property.
|
|
216
338
|
await seedDefaultBlocksField({ databaseId, ownerEmail, orgId, now, db });
|
|
339
|
+
await ensureDocumentFilesMembership(db, documentId, now, {
|
|
340
|
+
userEmail: getRequestUserEmail(),
|
|
341
|
+
orgId: orgId ?? undefined,
|
|
342
|
+
});
|
|
217
343
|
|
|
218
344
|
return databaseId;
|
|
219
345
|
}
|
|
@@ -14,10 +14,9 @@ import {
|
|
|
14
14
|
parseDocumentFavorite,
|
|
15
15
|
parseDocumentHideFromSearch,
|
|
16
16
|
} from "../server/lib/documents.js";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from "./_local-file-documents.js";
|
|
17
|
+
import { ensureDocumentFilesMembership } from "./_content-files.js";
|
|
18
|
+
import { resolveContentSpaceAccess } from "./_content-space-access.js";
|
|
19
|
+
import { provisionContentSpaces } from "./_content-spaces.js";
|
|
21
20
|
import { documentsPositionScope, withPositionLock } from "./_position-utils.js";
|
|
22
21
|
|
|
23
22
|
function nanoid(size = 12): string {
|
|
@@ -29,13 +28,6 @@ function nanoid(size = 12): string {
|
|
|
29
28
|
return id;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
function assertCanWriteAppState() {
|
|
33
|
-
if (getRequestUserEmail() || process.env.AGENT_USER_EMAIL) return;
|
|
34
|
-
throw new Error(
|
|
35
|
-
"Application state access requires an authenticated request context or AGENT_USER_EMAIL env var",
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
31
|
export default defineAction({
|
|
40
32
|
description: "Create a new document.",
|
|
41
33
|
schema: z.object({
|
|
@@ -43,6 +35,10 @@ export default defineAction({
|
|
|
43
35
|
.string()
|
|
44
36
|
.optional()
|
|
45
37
|
.describe("Pre-generated document ID (for optimistic UI)"),
|
|
38
|
+
spaceId: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Content space for a new top-level document"),
|
|
46
42
|
title: z.string().describe("Document title"),
|
|
47
43
|
content: z.string().optional().describe("Markdown content"),
|
|
48
44
|
description: z
|
|
@@ -66,21 +62,6 @@ export default defineAction({
|
|
|
66
62
|
}),
|
|
67
63
|
},
|
|
68
64
|
run: async (args) => {
|
|
69
|
-
if (await isContentLocalFileMode()) {
|
|
70
|
-
assertCanWriteAppState();
|
|
71
|
-
const doc = await createLocalFileDocument(args);
|
|
72
|
-
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
73
|
-
return {
|
|
74
|
-
...doc,
|
|
75
|
-
urlPath: `/page/${doc.id}`,
|
|
76
|
-
deepLink: buildDeepLink({
|
|
77
|
-
app: "content",
|
|
78
|
-
view: "editor",
|
|
79
|
-
params: { documentId: doc.id },
|
|
80
|
-
}),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
65
|
const title = args.title;
|
|
85
66
|
|
|
86
67
|
let content = args.content || "";
|
|
@@ -130,6 +111,28 @@ export default defineAction({
|
|
|
130
111
|
.where(eq(schema.documentShares.resourceId, parentId));
|
|
131
112
|
}
|
|
132
113
|
|
|
114
|
+
let spaceId: string;
|
|
115
|
+
if (parentId) {
|
|
116
|
+
const [parent] = await db
|
|
117
|
+
.select({ spaceId: schema.documents.spaceId })
|
|
118
|
+
.from(schema.documents)
|
|
119
|
+
.where(eq(schema.documents.id, parentId));
|
|
120
|
+
if (!parent?.spaceId) {
|
|
121
|
+
throw new Error(`Parent document "${parentId}" has no Content space`);
|
|
122
|
+
}
|
|
123
|
+
if (args.spaceId && args.spaceId !== parent.spaceId) {
|
|
124
|
+
throw new Error("Nested documents must use their parent Content space");
|
|
125
|
+
}
|
|
126
|
+
spaceId = parent.spaceId;
|
|
127
|
+
} else {
|
|
128
|
+
const provisioned = await provisionContentSpaces(db, currentUserEmail);
|
|
129
|
+
spaceId = args.spaceId ?? provisioned.personalSpaceId;
|
|
130
|
+
const spaceAccess = await resolveContentSpaceAccess(spaceId, "editor");
|
|
131
|
+
ownerEmail = currentUserEmail;
|
|
132
|
+
orgId = spaceAccess.space.orgId;
|
|
133
|
+
visibility = orgId ? "org" : "private";
|
|
134
|
+
}
|
|
135
|
+
|
|
133
136
|
const now = new Date().toISOString();
|
|
134
137
|
const id = args.id || nanoid();
|
|
135
138
|
|
|
@@ -154,39 +157,46 @@ export default defineAction({
|
|
|
154
157
|
|
|
155
158
|
const position = (maxPos[0]?.max ?? -1) + 1;
|
|
156
159
|
|
|
157
|
-
await db.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
160
|
+
await db.transaction(async (tx) => {
|
|
161
|
+
await tx.insert(schema.documents).values({
|
|
162
|
+
id,
|
|
163
|
+
spaceId,
|
|
164
|
+
ownerEmail,
|
|
165
|
+
orgId,
|
|
166
|
+
parentId,
|
|
167
|
+
title,
|
|
168
|
+
content,
|
|
169
|
+
description,
|
|
170
|
+
icon,
|
|
171
|
+
position,
|
|
172
|
+
isFavorite: 0,
|
|
173
|
+
hideFromSearch,
|
|
174
|
+
visibility,
|
|
175
|
+
createdAt: now,
|
|
176
|
+
updatedAt: now,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (inheritedShares.length > 0) {
|
|
180
|
+
await tx.insert(schema.documentShares).values(
|
|
181
|
+
inheritedShares.map((share) => ({
|
|
182
|
+
id: nanoid(),
|
|
183
|
+
resourceId: id,
|
|
184
|
+
principalType: share.principalType,
|
|
185
|
+
principalId: share.principalId,
|
|
186
|
+
role: share.role,
|
|
187
|
+
createdBy: currentUserEmail,
|
|
188
|
+
createdAt: now,
|
|
189
|
+
})),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
await ensureDocumentFilesMembership(tx, id, now, {
|
|
193
|
+
userEmail: currentUserEmail,
|
|
194
|
+
orgId: orgId ?? undefined,
|
|
195
|
+
});
|
|
172
196
|
});
|
|
173
197
|
},
|
|
174
198
|
);
|
|
175
199
|
|
|
176
|
-
if (inheritedShares.length > 0) {
|
|
177
|
-
await db.insert(schema.documentShares).values(
|
|
178
|
-
inheritedShares.map((share) => ({
|
|
179
|
-
id: nanoid(),
|
|
180
|
-
resourceId: id,
|
|
181
|
-
principalType: share.principalType,
|
|
182
|
-
principalId: share.principalId,
|
|
183
|
-
role: share.role,
|
|
184
|
-
createdBy: currentUserEmail,
|
|
185
|
-
createdAt: now,
|
|
186
|
-
})),
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
200
|
const [doc] = await db
|
|
191
201
|
.select()
|
|
192
202
|
.from(schema.documents)
|
|
@@ -10,6 +10,7 @@ import { nanoid } from "./_property-utils.js";
|
|
|
10
10
|
import {
|
|
11
11
|
createContentDatabaseRecord,
|
|
12
12
|
databaseTitleForPage,
|
|
13
|
+
resolveContentDatabaseSpace,
|
|
13
14
|
} from "./create-content-database.js";
|
|
14
15
|
|
|
15
16
|
function createInlineDatabaseBlockId(): string {
|
|
@@ -33,6 +34,10 @@ export default defineAction({
|
|
|
33
34
|
const ownerBlockId = createInlineDatabaseBlockId();
|
|
34
35
|
let databaseId: string | null = null;
|
|
35
36
|
let databaseDocumentId: string | null = null;
|
|
37
|
+
const spaceId = await resolveContentDatabaseSpace(
|
|
38
|
+
{ parentId: hostDocumentId },
|
|
39
|
+
db,
|
|
40
|
+
);
|
|
36
41
|
|
|
37
42
|
await db.transaction(async (tx) => {
|
|
38
43
|
databaseId = await createContentDatabaseRecord(
|
|
@@ -41,7 +46,7 @@ export default defineAction({
|
|
|
41
46
|
title: databaseTitleForPage(title),
|
|
42
47
|
description,
|
|
43
48
|
},
|
|
44
|
-
{ db: tx },
|
|
49
|
+
{ db: tx, spaceId },
|
|
45
50
|
);
|
|
46
51
|
|
|
47
52
|
const [updated] = await tx
|
|
@@ -14,6 +14,9 @@ export default defineAction({
|
|
|
14
14
|
}),
|
|
15
15
|
run: async ({ databaseId }) => {
|
|
16
16
|
const { database } = await assertContentDatabaseLifecycleAccess(databaseId);
|
|
17
|
+
if (database.systemRole) {
|
|
18
|
+
throw new Error("System Content databases cannot be deleted");
|
|
19
|
+
}
|
|
17
20
|
const db = getDb();
|
|
18
21
|
const deletedAt = database.deletedAt ?? new Date().toISOString();
|
|
19
22
|
|
|
@@ -6,11 +6,6 @@ import { z } from "zod";
|
|
|
6
6
|
|
|
7
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
8
8
|
import { chunks } from "./_batch-utils.js";
|
|
9
|
-
import {
|
|
10
|
-
deleteLocalFileDocument,
|
|
11
|
-
isLocalDocumentId,
|
|
12
|
-
isContentLocalFileMode,
|
|
13
|
-
} from "./_local-file-documents.js";
|
|
14
9
|
|
|
15
10
|
const DELETE_BATCH_SIZE = 90;
|
|
16
11
|
|
|
@@ -365,16 +360,17 @@ export default defineAction({
|
|
|
365
360
|
const id = args.id;
|
|
366
361
|
if (!id) throw new Error("--id is required");
|
|
367
362
|
|
|
368
|
-
if ((await isContentLocalFileMode()) && isLocalDocumentId(id)) {
|
|
369
|
-
const result = await deleteLocalFileDocument(id);
|
|
370
|
-
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
371
|
-
return result;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
363
|
const access = await assertAccess("document", id, "admin");
|
|
375
364
|
const existing = access.resource;
|
|
376
365
|
|
|
377
366
|
const db = getDb();
|
|
367
|
+
const [systemDatabase] = await db
|
|
368
|
+
.select({ systemRole: schema.contentDatabases.systemRole })
|
|
369
|
+
.from(schema.contentDatabases)
|
|
370
|
+
.where(eq(schema.contentDatabases.documentId, id));
|
|
371
|
+
if (systemDatabase?.systemRole) {
|
|
372
|
+
throw new Error("System Content database documents cannot be deleted");
|
|
373
|
+
}
|
|
378
374
|
const deleted = await deleteDocumentRecursive(
|
|
379
375
|
db,
|
|
380
376
|
id,
|