mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 17:33:51 +02:00
Support restoring archived LLM Wiki spaces
This commit is contained in:
1 parent
dc015bea9a
commit
7bbc705413
3 files changed
+69
-4
No files matched your search
@@ -2829,7 +2829,7 @@ function SpaceRowMenu({
|
||||
|
||||
const handleArchive = async () => {
|
||||
if (!companyId || isDefault || busy) return;
|
||||
if (typeof window !== "undefined" && !window.confirm(`Archive ${space.displayName}? Pages remain on disk; you can restore from settings.`)) {
|
||||
if (typeof window !== "undefined" && !window.confirm(`Archive ${space.displayName}? Pages remain on disk; you can restore later through the plugin API or by un-archiving from the database.`)) {
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
@@ -6768,7 +6768,7 @@ function SpaceEditCard({
|
||||
|
||||
async function archive() {
|
||||
if (!companyId || isDefault || isOnlySpace || archiveBusy) return;
|
||||
if (typeof window !== "undefined" && !window.confirm(`Archive ${space.displayName}? Pages stay on disk; you can restore by un-archiving from the database.`)) {
|
||||
if (typeof window !== "undefined" && !window.confirm(`Archive ${space.displayName}? Pages stay on disk; you can restore later through the plugin API or by un-archiving from the database.`)) {
|
||||
return;
|
||||
}
|
||||
setArchiveBusy(true);
|
||||
|
||||
@@ -1367,6 +1367,25 @@ export async function resolveSpace(ctx: PluginContext, input: SpaceInput): Promi
|
||||
return wikiSpaceFromRow(rows[0]);
|
||||
}
|
||||
|
||||
async function resolveSpaceAnyStatus(ctx: PluginContext, input: SpaceInput): Promise<WikiSpace> {
|
||||
const wikiId = normalizeWikiId(input.wikiId);
|
||||
const slug = normalizeSpaceSlug(input.spaceSlug);
|
||||
if (slug === DEFAULT_SPACE_SLUG) {
|
||||
return ensureDefaultSpace(ctx, { companyId: input.companyId, wikiId });
|
||||
}
|
||||
const rows = await ctx.db.query<WikiSpaceRow>(
|
||||
`SELECT id, company_id, wiki_id, slug, display_name, space_type, folder_mode, root_folder_key,
|
||||
path_prefix, configured_root_path, access_scope, owner_user_id, owner_agent_id, team_key,
|
||||
settings, status, created_at::text AS created_at, updated_at::text AS updated_at
|
||||
FROM ${spaceTable(ctx)}
|
||||
WHERE company_id = $1 AND wiki_id = $2 AND slug = $3
|
||||
LIMIT 1`,
|
||||
[input.companyId, wikiId, slug],
|
||||
);
|
||||
if (!rows[0]) throw new Error(`LLM Wiki space not found: ${slug}`);
|
||||
return wikiSpaceFromRow(rows[0]);
|
||||
}
|
||||
|
||||
export async function listSpaces(ctx: PluginContext, input: { companyId: string; wikiId?: string | null }): Promise<{ spaces: WikiSpace[] }> {
|
||||
const wikiId = normalizeWikiId(input.wikiId);
|
||||
await ensureDefaultSpace(ctx, { companyId: input.companyId, wikiId });
|
||||
@@ -1440,12 +1459,12 @@ export async function createSpace(ctx: PluginContext, input: CreateSpaceInput):
|
||||
}
|
||||
|
||||
export async function updateSpace(ctx: PluginContext, input: UpdateSpaceInput): Promise<{ status: "ok"; space: WikiSpace }> {
|
||||
const space = await resolveSpace(ctx, input);
|
||||
const nextDisplayName = stringField(input.displayName);
|
||||
const nextStatus = input.status ?? null;
|
||||
if (nextStatus !== null && nextStatus !== "active" && nextStatus !== "archived") {
|
||||
throw new Error("LLM Wiki space status must be active or archived.");
|
||||
}
|
||||
const space = nextStatus === "active" ? await resolveSpaceAnyStatus(ctx, input) : await resolveSpace(ctx, input);
|
||||
const nextDisplayName = stringField(input.displayName);
|
||||
if (space.slug === DEFAULT_SPACE_SLUG && nextStatus === "archived") {
|
||||
throw new Error("The default LLM Wiki space cannot be archived.");
|
||||
}
|
||||
|
||||
@@ -2867,6 +2867,52 @@ Duplicate headings receive stable suffixes.
|
||||
expect(archived).toBe(true);
|
||||
});
|
||||
|
||||
it("restores an archived non-default space through update-space", async () => {
|
||||
const harness = createTestHarness({ manifest });
|
||||
const spaceRow = wikiSpaceRow({
|
||||
id: "77777777-7777-4777-8777-7777777777b2",
|
||||
slug: "qa-space",
|
||||
displayName: "QA Space",
|
||||
pathPrefix: "spaces/qa-space",
|
||||
status: "archived",
|
||||
});
|
||||
let restored = false;
|
||||
const originalQuery = harness.ctx.db.query.bind(harness.ctx.db);
|
||||
harness.ctx.db.query = async <T,>(sql: string, params?: unknown[]) => {
|
||||
harness.dbQueries.push({ sql, params });
|
||||
if (sql.includes("wiki_spaces") && params?.[2] === "qa-space") {
|
||||
if (!restored && sql.includes("status <> 'archived'")) return [] as T[];
|
||||
return [{
|
||||
...spaceRow,
|
||||
status: restored ? "active" : "archived",
|
||||
}] as T[];
|
||||
}
|
||||
return originalQuery<T>(sql, params);
|
||||
};
|
||||
const originalExecute = harness.ctx.db.execute.bind(harness.ctx.db);
|
||||
harness.ctx.db.execute = async (sql: string, params?: unknown[]) => {
|
||||
if (sql.includes("UPDATE") && sql.includes("wiki_spaces") && params?.includes("active")) restored = true;
|
||||
return originalExecute(sql, params);
|
||||
};
|
||||
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
const updated = await harness.performAction<{
|
||||
status: string;
|
||||
space: { slug: string; status: string };
|
||||
}>("update-space", {
|
||||
companyId: COMPANY_ID,
|
||||
spaceSlug: "qa-space",
|
||||
status: "active",
|
||||
});
|
||||
|
||||
expect(updated.status).toBe("ok");
|
||||
expect(updated.space).toMatchObject({
|
||||
slug: "qa-space",
|
||||
status: "active",
|
||||
});
|
||||
expect(restored).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects unknown space statuses through update-space", async () => {
|
||||
const harness = createTestHarness({ manifest });
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
|
||||
Reference in new issue
Block a user