feat(core): merge backup core changes with post-split functionality

This commit is contained in:
Dotta
2026-03-02 16:43:59 -06:00
parent 7642743e62
commit 83be94361c
25 changed files with 1125 additions and 46 deletions

View File

@@ -1,7 +1,14 @@
import { and, asc, desc, eq, inArray } from "drizzle-orm";
import type { Db } from "@paperclip/db";
import { projects, projectGoals, goals, projectWorkspaces } from "@paperclip/db";
import { PROJECT_COLORS, type ProjectGoalRef, type ProjectWorkspace } from "@paperclip/shared";
import {
PROJECT_COLORS,
deriveProjectUrlKey,
isUuidLike,
normalizeProjectUrlKey,
type ProjectGoalRef,
type ProjectWorkspace,
} from "@paperclip/shared";
type ProjectRow = typeof projects.$inferSelect;
type ProjectWorkspaceRow = typeof projectWorkspaces.$inferSelect;
@@ -17,6 +24,7 @@ type CreateWorkspaceInput = {
type UpdateWorkspaceInput = Partial<CreateWorkspaceInput>;
interface ProjectWithGoals extends ProjectRow {
urlKey: string;
goalIds: string[];
goals: ProjectGoalRef[];
workspaces: ProjectWorkspace[];
@@ -52,7 +60,12 @@ async function attachGoals(db: Db, rows: ProjectRow[]): Promise<ProjectWithGoals
return rows.map((r) => {
const g = map.get(r.id) ?? [];
return { ...r, goalIds: g.map((x) => x.id), goals: g } as ProjectWithGoals;
return {
...r,
urlKey: deriveProjectUrlKey(r.name, r.id),
goalIds: g.map((x) => x.id),
goals: g,
} as ProjectWithGoals;
});
}
@@ -314,7 +327,11 @@ export function projectService(db: Db) {
.delete(projects)
.where(eq(projects.id, id))
.returning()
.then((rows) => rows[0] ?? null),
.then((rows) => {
const row = rows[0] ?? null;
if (!row) return null;
return { ...row, urlKey: deriveProjectUrlKey(row.name, row.id) };
}),
listWorkspaces: async (projectId: string): Promise<ProjectWorkspace[]> => {
const rows = await db
@@ -555,5 +572,47 @@ export function projectService(db: Db) {
return removed ? toWorkspace(removed) : null;
},
resolveByReference: async (companyId: string, reference: string) => {
const raw = reference.trim();
if (raw.length === 0) {
return { project: null, ambiguous: false } as const;
}
if (isUuidLike(raw)) {
const row = await db
.select({ id: projects.id, companyId: projects.companyId, name: projects.name })
.from(projects)
.where(and(eq(projects.id, raw), eq(projects.companyId, companyId)))
.then((rows) => rows[0] ?? null);
if (!row) return { project: null, ambiguous: false } as const;
return {
project: { id: row.id, companyId: row.companyId, urlKey: deriveProjectUrlKey(row.name, row.id) },
ambiguous: false,
} as const;
}
const urlKey = normalizeProjectUrlKey(raw);
if (!urlKey) {
return { project: null, ambiguous: false } as const;
}
const rows = await db
.select({ id: projects.id, companyId: projects.companyId, name: projects.name })
.from(projects)
.where(eq(projects.companyId, companyId));
const matches = rows.filter((row) => deriveProjectUrlKey(row.name, row.id) === urlKey);
if (matches.length === 1) {
const match = matches[0]!;
return {
project: { id: match.id, companyId: match.companyId, urlKey: deriveProjectUrlKey(match.name, match.id) },
ambiguous: false,
} as const;
}
if (matches.length > 1) {
return { project: null, ambiguous: true } as const;
}
return { project: null, ambiguous: false } as const;
},
};
}