147 lines
5.1 KiB
TypeScript
147 lines
5.1 KiB
TypeScript
import {
|
|
DEFAULT_AI_PROMPT_SEEDS,
|
|
defaultAiPromptId,
|
|
} from "@edgeever/shared/ai-prompt-seeds";
|
|
import {
|
|
DEFAULT_MEMO_TEMPLATE_SEEDS,
|
|
defaultMemoTemplateId,
|
|
localizeMemoTemplateSeed,
|
|
} from "@edgeever/shared/memo-template-seeds";
|
|
import { createId, isoNow } from "./entity-utils";
|
|
import type {
|
|
DatabaseAdapter,
|
|
PreparedStatementAdapter,
|
|
} from "./storage-contract";
|
|
|
|
export const DEFAULT_WORKSPACE_ID = "ws_default";
|
|
|
|
export type UserWorkspace = {
|
|
workspaceId: string;
|
|
role: "owner" | "member";
|
|
};
|
|
|
|
export type DefaultNotebookRow = {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
color: string;
|
|
sortOrder: number;
|
|
};
|
|
|
|
export const createDefaultNotebookRows = (workspaceId: string): DefaultNotebookRow[] => [
|
|
{ id: `${workspaceId}_inbox`, name: "等待分类", slug: "inbox", color: "#0f766e", sortOrder: 10 },
|
|
{ id: `${workspaceId}_projects`, name: "工作项目", slug: "work-projects", color: "#2563eb", sortOrder: 20 },
|
|
{ id: `${workspaceId}_learning`, name: "学习资料", slug: "learning-resources", color: "#7c3aed", sortOrder: 30 },
|
|
{ id: `${workspaceId}_creative`, name: "灵感创作", slug: "creative-ideas", color: "#db2777", sortOrder: 40 },
|
|
{ id: `${workspaceId}_personal`, name: "生活个人", slug: "personal-life", color: "#ea580c", sortOrder: 50 },
|
|
];
|
|
|
|
// Template use and editing derive the TipTap document from content_markdown.
|
|
// Keeping this initial value static avoids loading the Markdown parser merely
|
|
// to provision a workspace.
|
|
const EMPTY_TEMPLATE_CONTENT_JSON = JSON.stringify({ type: "doc", content: [] });
|
|
|
|
export const createWorkspaceDefaultSeedStatements = (
|
|
db: DatabaseAdapter,
|
|
workspaceId: string,
|
|
now = isoNow(),
|
|
locale?: string | null,
|
|
): PreparedStatementAdapter[] => [
|
|
...DEFAULT_MEMO_TEMPLATE_SEEDS.map((seed) => {
|
|
const localized = localizeMemoTemplateSeed(seed, locale);
|
|
return db.prepare(
|
|
`INSERT OR IGNORE INTO memo_templates (
|
|
id, workspace_id, name, description, title, content_json, content_markdown, tags_json, created_at, updated_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
).bind(
|
|
defaultMemoTemplateId(workspaceId, seed.key),
|
|
workspaceId,
|
|
localized.title,
|
|
localized.description,
|
|
localized.title,
|
|
EMPTY_TEMPLATE_CONTENT_JSON,
|
|
localized.contentMarkdown,
|
|
JSON.stringify(["template", seed.tag]),
|
|
now,
|
|
now,
|
|
);
|
|
}),
|
|
...DEFAULT_AI_PROMPT_SEEDS.map((seed) => db.prepare(
|
|
`INSERT OR IGNORE INTO ai_prompt_templates (
|
|
id, workspace_id, seed_key, action, parameter_kind, result_mode,
|
|
name, description, instruction,
|
|
name_customized, description_customized, instruction_customized,
|
|
created_at, updated_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, ?, ?)`,
|
|
).bind(
|
|
defaultAiPromptId(workspaceId, seed.key),
|
|
workspaceId,
|
|
seed.key,
|
|
seed.action,
|
|
seed.parameterKind,
|
|
seed.resultMode,
|
|
seed.name,
|
|
seed.description,
|
|
seed.instruction,
|
|
now,
|
|
now,
|
|
)),
|
|
];
|
|
|
|
export const ensureUserWorkspace = async (
|
|
db: DatabaseAdapter,
|
|
userId: string,
|
|
username: string,
|
|
locale?: string | null,
|
|
): Promise<UserWorkspace> => {
|
|
const existing = await db.prepare(
|
|
`SELECT workspace_id, role FROM workspace_members WHERE user_id = ? LIMIT 1`,
|
|
).bind(userId).first<{ workspace_id: string; role: "owner" | "member" }>();
|
|
if (existing) {
|
|
return { workspaceId: existing.workspace_id, role: existing.role };
|
|
}
|
|
|
|
const defaultOwner = await db.prepare(
|
|
`SELECT user_id FROM workspace_members WHERE workspace_id = ? LIMIT 1`,
|
|
).bind(DEFAULT_WORKSPACE_ID).first<{ user_id: string }>();
|
|
if (!defaultOwner) {
|
|
await db.prepare(
|
|
`INSERT OR IGNORE INTO workspace_members (workspace_id, user_id, role) VALUES (?, ?, 'owner')`,
|
|
).bind(DEFAULT_WORKSPACE_ID, userId).run();
|
|
const claimed = await db.prepare(
|
|
`SELECT workspace_id, role FROM workspace_members WHERE user_id = ? LIMIT 1`,
|
|
).bind(userId).first<{ workspace_id: string; role: "owner" | "member" }>();
|
|
if (claimed) {
|
|
await db.batch(createWorkspaceDefaultSeedStatements(db, claimed.workspace_id, isoNow(), locale));
|
|
return { workspaceId: claimed.workspace_id, role: claimed.role };
|
|
}
|
|
}
|
|
|
|
const workspaceId = createId("ws");
|
|
const now = isoNow();
|
|
const notebooks = createDefaultNotebookRows(workspaceId);
|
|
await db.batch([
|
|
db.prepare(
|
|
`INSERT INTO workspaces (id, name, is_personal, created_at, updated_at) VALUES (?, ?, 1, ?, ?)`,
|
|
).bind(workspaceId, `${username}'s workspace`, now, now),
|
|
db.prepare(
|
|
`INSERT INTO workspace_members (workspace_id, user_id, role, created_at) VALUES (?, ?, 'member', ?)`,
|
|
).bind(workspaceId, userId, now),
|
|
...notebooks.map((notebook) => db.prepare(
|
|
`INSERT INTO notebooks (id, workspace_id, parent_id, name, slug, icon, color, sort_order, created_at, updated_at)
|
|
VALUES (?, ?, NULL, ?, ?, 'notebook', ?, ?, ?, ?)`,
|
|
).bind(
|
|
notebook.id,
|
|
workspaceId,
|
|
notebook.name,
|
|
notebook.slug,
|
|
notebook.color,
|
|
notebook.sortOrder,
|
|
now,
|
|
now,
|
|
)),
|
|
...createWorkspaceDefaultSeedStatements(db, workspaceId, now, locale),
|
|
]);
|
|
return { workspaceId, role: "member" };
|
|
};
|