100 lines
2.8 KiB
TypeScript
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;
|
|
}
|