118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
export type DocumentVisibilityScope = "private" | "shared" | "public" | null | undefined;
|
|||
|
|
|
||
|
|
export type DocumentVisibilityRecord = {
|
||
|
|
id: string;
|
||
|
|
user_id?: string | null;
|
||
|
|
parent_id?: string | null;
|
||
|
|
access_scope?: DocumentVisibilityScope;
|
||
|
|
deleted_at?: string | null;
|
||
|
|
};
|
||
|
|
|
||
|
|
export type DocumentVisibilityShareRecord = {
|
||
|
|
document_id: string;
|
||
|
|
include_descendants?: boolean | null;
|
||
|
|
};
|
||
|
|
|
||
|
|
type BuildVisibleDocumentIdsInput = {
|
||
|
|
userId: string;
|
||
|
|
docs: readonly DocumentVisibilityRecord[];
|
||
|
|
directShares: readonly DocumentVisibilityShareRecord[];
|
||
|
|
directGroupShares: readonly DocumentVisibilityShareRecord[];
|
||
|
|
includeDeletedDocuments?: boolean;
|
||
|
|
};
|
||
|
|
|
||
|
|
function normalizeDocumentId(value: string | null | undefined): string | null {
|
||
|
|
const trimmed = String(value ?? "").trim();
|
||
|
|
return trimmed.length > 0 ? trimmed : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildInheritedShareMap(
|
||
|
|
shares: readonly DocumentVisibilityShareRecord[],
|
||
|
|
): Map<string, { includeDescendants: boolean }> {
|
||
|
|
const result = new Map<string, { includeDescendants: boolean }>();
|
||
|
|
for (const share of shares) {
|
||
|
|
const documentId = normalizeDocumentId(share.document_id);
|
||
|
|
if (!documentId) continue;
|
||
|
|
const existing = result.get(documentId);
|
||
|
|
if (!existing) {
|
||
|
|
result.set(documentId, {
|
||
|
|
includeDescendants: Boolean(share.include_descendants),
|
||
|
|
});
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
existing.includeDescendants = existing.includeDescendants || Boolean(share.include_descendants);
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
function canAccessByInheritedShare(input: {
|
||
|
|
docId: string;
|
||
|
|
parentById: Map<string, string | null>;
|
||
|
|
directShareMap: Map<string, { includeDescendants: boolean }>;
|
||
|
|
cache: Map<string, boolean>;
|
||
|
|
}): boolean {
|
||
|
|
const cached = input.cache.get(input.docId);
|
||
|
|
if (typeof cached === "boolean") return cached;
|
||
|
|
if (input.directShareMap.has(input.docId)) {
|
||
|
|
input.cache.set(input.docId, true);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
let parentId = input.parentById.get(input.docId) ?? null;
|
||
|
|
for (let depth = 0; depth < 60 && parentId; depth += 1) {
|
||
|
|
const parentShare = input.directShareMap.get(parentId);
|
||
|
|
if (parentShare && parentShare.includeDescendants) {
|
||
|
|
input.cache.set(input.docId, true);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
parentId = input.parentById.get(parentId) ?? null;
|
||
|
|
}
|
||
|
|
input.cache.set(input.docId, false);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function buildVisibleDocumentIds(input: BuildVisibleDocumentIdsInput): Set<string> {
|
||
|
|
const candidateDocs = input.includeDeletedDocuments
|
||
|
|
? [...input.docs]
|
||
|
|
: input.docs.filter((doc) => doc.deleted_at == null);
|
||
|
|
|
||
|
|
const parentById = new Map<string, string | null>();
|
||
|
|
for (const doc of candidateDocs) {
|
||
|
|
parentById.set(doc.id, normalizeDocumentId(doc.parent_id) ?? null);
|
||
|
|
}
|
||
|
|
|
||
|
|
const directShares = buildInheritedShareMap(input.directShares);
|
||
|
|
const directGroupShares = buildInheritedShareMap(input.directGroupShares);
|
||
|
|
const shareAccessCache = new Map<string, boolean>();
|
||
|
|
const groupAccessCache = new Map<string, boolean>();
|
||
|
|
const visible = new Set<string>();
|
||
|
|
|
||
|
|
for (const doc of candidateDocs) {
|
||
|
|
if (String(doc.user_id ?? "") === input.userId) {
|
||
|
|
visible.add(doc.id);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (doc.access_scope === "public") {
|
||
|
|
visible.add(doc.id);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (
|
||
|
|
canAccessByInheritedShare({
|
||
|
|
docId: doc.id,
|
||
|
|
parentById,
|
||
|
|
directShareMap: directShares,
|
||
|
|
cache: shareAccessCache,
|
||
|
|
}) ||
|
||
|
|
canAccessByInheritedShare({
|
||
|
|
docId: doc.id,
|
||
|
|
parentById,
|
||
|
|
directShareMap: directGroupShares,
|
||
|
|
cache: groupAccessCache,
|
||
|
|
})
|
||
|
|
) {
|
||
|
|
visible.add(doc.id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return visible;
|
||
|
|
}
|