fix(trash): list nested trashed roots

This commit is contained in:
saltbo
2026-06-06 12:16:11 -04:00
parent 25ab67208b
commit 1bd905366d
3 changed files with 44 additions and 1 deletions
+25
View File
@@ -482,6 +482,31 @@ describe('Objects API', () => {
expect(childBody.status).toBe('active')
})
it('GET /api/objects?status=trashed returns trashed folder roots nested under active parents', async () => {
const { app, db } = await createTestApp()
const headers = await authedHeaders(app)
await insertStorage(db)
const orgId = await getOrgId(db)
await insertFolder(db, orgId, { id: 'media', name: 'Media' })
await insertFolder(db, orgId, { id: 'music', name: 'Music', parent: 'Media' })
await insertFolder(db, orgId, { id: 'album', name: 'Album', parent: 'Media/Music' })
await insertFile(db, orgId, { id: 'track', name: 'track.flac', parent: 'Media/Music/Album' })
const trashRes = await app.request('/api/objects/batch', {
method: 'PATCH',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'trash', ids: ['album'] }),
})
expect(trashRes.status).toBe(200)
const res = await app.request('/api/objects?status=trashed', { headers })
expect(res.status).toBe(200)
const body = (await res.json()) as { items: Array<{ id: string }>; total: number }
expect(body.total).toBe(1)
expect(body.items.map((item) => item.id)).toEqual(['album'])
})
it('DELETE /api/trash purges all trashed items', async () => {
const { app, db } = await createTestApp()
const headers = await authedHeaders(app)
+18 -1
View File
@@ -112,6 +112,16 @@ export async function listMatters(
filters: ListFilters,
): Promise<{ items: Matter[]; total: number; page: number; pageSize: number }> {
const offset = (filters.page - 1) * filters.pageSize
if (filters.status === 'trashed' && !filters.search && !filters.typeFilter) {
const roots = await listTrashedRoots(db, orgId)
return {
items: roots.slice(offset, offset + filters.pageSize),
total: roots.length,
page: filters.page,
pageSize: filters.pageSize,
}
}
const conditions = [eq(matters.orgId, orgId), eq(matters.status, filters.status)]
const typeCond = filters.typeFilter ? typeFilterCondition(filters.typeFilter) : undefined
if (filters.search) {
@@ -689,5 +699,12 @@ export async function listTrashedRoots(db: Database, orgId: string): Promise<Mat
.where(and(eq(matters.orgId, orgId), eq(matters.status, 'trashed')))
const trashedPaths = new Set(all.map((m) => buildPath(m.parent, m.name)))
return all.filter((m) => !trashedPaths.has(m.parent))
return all
.filter((m) => !trashedPaths.has(m.parent))
.sort((a, b) => {
const aTrashedAt = a.trashedAt ?? 0
const bTrashedAt = b.trashedAt ?? 0
if (aTrashedAt !== bTrashedAt) return bTrashedAt - aTrashedAt
return b.createdAt.getTime() - a.createdAt.getTime()
})
}
@@ -10,6 +10,7 @@ export function useFileMutations(currentPath: string) {
const queryClient = useQueryClient()
const invalidate = () => {
queryClient.invalidateQueries({ queryKey: ['objects', 'active', 'path', currentPath] })
queryClient.invalidateQueries({ queryKey: ['objects', 'trashed'] })
queryClient.invalidateQueries({ queryKey: ['user', 'quota'] })
}