mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-24 23:22:31 +08:00
- Add `shares` and `share_recipients` tables to Drizzle schema with indices - Add migration 0010_shares.sql for shares/share_recipients tables - Add `server/lib/password.ts` extracting scrypt hash/verify from auth.ts to eliminate duplicate crypto params across services - Add `server/services/share.ts` implementing full CRUD + atomic counters: createShare, getShareByToken, incrementViews, incrementDownloadsAtomic (atomic SQL UPDATE), listSharesByCreator, revokeShare, cascadeDeleteByMatter - Add `shared/schemas/share.ts` Zod validation schemas - Export Share, ShareKind, ShareRecipient from shared/types - Extend `purge.ts` to cascade-delete shares on matter hard-delete - Add 38 integration tests and CF Workers atomic counter race tests Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
import { DirType } from '../../shared/constants'
|
|
import type { Storage as S3Storage } from '../../shared/types'
|
|
import type { Database } from '../platform/interface'
|
|
import { decrementUsage, type Matter, purgeMatters } from './matter'
|
|
import { S3Service } from './s3'
|
|
import { cascadeDeleteByMatter } from './share'
|
|
import { getStorage } from './storage'
|
|
|
|
const s3 = new S3Service()
|
|
|
|
export async function purgeRecursively(db: Database, orgId: string, matters: Matter[]): Promise<number> {
|
|
const keysByStorage = new Map<string, { storage: S3Storage | null; keys: string[] }>()
|
|
const bytesByStorage = new Map<string, number>()
|
|
let totalBytes = 0
|
|
|
|
for (const m of matters) {
|
|
const size = m.size ?? 0
|
|
if (m.dirtype === DirType.FILE && size > 0) {
|
|
bytesByStorage.set(m.storageId, (bytesByStorage.get(m.storageId) ?? 0) + size)
|
|
totalBytes += size
|
|
}
|
|
if (!m.object) continue
|
|
let entry = keysByStorage.get(m.storageId)
|
|
if (!entry) {
|
|
const storage = (await getStorage(db, m.storageId)) as unknown as S3Storage | null
|
|
entry = { storage, keys: [] }
|
|
keysByStorage.set(m.storageId, entry)
|
|
}
|
|
entry.keys.push(m.object)
|
|
}
|
|
|
|
for (const { storage, keys } of keysByStorage.values()) {
|
|
if (storage && keys.length > 0) await s3.deleteObjects(storage, keys)
|
|
}
|
|
|
|
for (const m of matters) {
|
|
await cascadeDeleteByMatter(db, m.id)
|
|
}
|
|
|
|
await purgeMatters(
|
|
db,
|
|
orgId,
|
|
matters.map((m) => m.id),
|
|
)
|
|
await decrementUsage(db, orgId, bytesByStorage, totalBytes)
|
|
return matters.length
|
|
}
|