Files
mnote/wolai-frontend/convex/_utils/documentRecord.ts
T
2026-04-26 04:29:23 +08:00

100 lines
2.8 KiB
TypeScript

type DocumentRecordLike = {
_id: unknown;
id?: string | null;
user_id?: string | null;
parent_id?: string | null;
deleted_at?: string | null;
created_at?: string | null;
updated_at?: string | null;
};
export function compareDocumentCanonicalOrder(a: DocumentRecordLike, b: DocumentRecordLike): number {
const aDeleted = a?.deleted_at != null;
const bDeleted = b?.deleted_at != null;
if (aDeleted !== bDeleted) {
return aDeleted ? 1 : -1;
}
const updatedA = String(a?.updated_at ?? "");
const updatedB = String(b?.updated_at ?? "");
if (updatedA !== updatedB) {
return updatedB.localeCompare(updatedA);
}
const createdA = String(a?.created_at ?? "");
const createdB = String(b?.created_at ?? "");
if (createdA !== createdB) {
return createdB.localeCompare(createdA);
}
return String(a?._id ?? "").localeCompare(String(b?._id ?? ""));
}
export function pickCanonicalDocumentRecord<T extends DocumentRecordLike>(records: T[]): T | null {
if (records.length === 0) return null;
return [...records].sort(compareDocumentCanonicalOrder)[0] ?? null;
}
export function pickCanonicalDocumentRecordsByBusinessId<
T extends DocumentRecordLike & { id?: string | null },
>(records: readonly T[]): T[] {
const grouped = new Map<string, T[]>();
for (const record of records) {
const businessId = String(record.id ?? "").trim();
if (!businessId) continue;
const bucket = grouped.get(businessId);
if (bucket) {
bucket.push(record);
} else {
grouped.set(businessId, [record]);
}
}
const result: T[] = [];
for (const bucket of grouped.values()) {
const canonical = pickCanonicalDocumentRecord([...bucket]);
if (canonical) {
result.push(canonical);
}
}
return result;
}
export async function getCanonicalDocumentByBusinessId<T extends DocumentRecordLike>(
ctx: any,
documentId: string,
): Promise<T | null> {
const records = await ctx.db
.query("documents")
.withIndex("by_document_id", (q: any) => q.eq("id", documentId))
.collect();
return pickCanonicalDocumentRecord(records) as T | null;
}
export async function getCanonicalParentDocumentId(
ctx: any,
documentId: string | null | undefined,
): Promise<string | null> {
const normalizedId = String(documentId ?? "").trim();
if (!normalizedId) return null;
const doc = await getCanonicalDocumentByBusinessId<DocumentRecordLike>(ctx, normalizedId);
return doc?.parent_id ?? null;
}
export async function requireCanonicalOwnedDocument<T extends DocumentRecordLike & { user_id?: string | null }>(
ctx: any,
documentId: string,
userId: string,
): Promise<T> {
const doc = await getCanonicalDocumentByBusinessId<T>(ctx, documentId);
if (!doc) {
throw new Error("页面不存在");
}
if (String(doc.user_id ?? "") !== String(userId)) {
throw new Error("无权限");
}
return doc;
}