fixing sum' file logic flaws & device UI stuff stuff

This commit is contained in:
user
2026-03-01 19:29:27 +02:00
parent 48792692ff
commit 3940130dad
4 changed files with 278 additions and 134 deletions

View File

@@ -214,46 +214,59 @@ export class FileController {
attributes: { "app.user.id": userId },
fn: () =>
this.fileRepo
.listReferencedObjectKeysForUser(fctx, userId)
.andThen((referencedKeys) => {
const referencedSet = new Set(referencedKeys);
return ResultAsync.combine([
this.storageRepo.listObjectKeys(
fctx,
`uploads/${userId}/`,
),
this.storageRepo.listObjectKeys(
fctx,
`thumbnails/${userId}/`,
),
]).andThen(([uploadKeys, thumbnailKeys]) => {
const existingStorageKeys = [
...new Set([...uploadKeys, ...thumbnailKeys]),
];
.listMobileMediaReferencedObjectKeysForUser(fctx, userId)
.andThen((referencedKeys) =>
this.fileRepo
.listMobileMediaDanglingFileIdsForUser(fctx, userId)
.andThen((danglingFileIds) => {
const referencedSet = new Set(referencedKeys);
return ResultAsync.combine([
this.storageRepo.listObjectKeys(
fctx,
`uploads/${userId}/`,
),
this.storageRepo.listObjectKeys(
fctx,
`thumbnails/${userId}/`,
),
]).andThen(([uploadKeys, thumbnailKeys]) => {
const existingStorageKeys = [
...new Set([...uploadKeys, ...thumbnailKeys]),
];
const danglingKeys = existingStorageKeys.filter(
(key) => !referencedSet.has(key),
);
const danglingKeys = existingStorageKeys.filter(
(key) => !referencedSet.has(key),
);
if (danglingKeys.length === 0) {
return okAsync({
scanned: existingStorageKeys.length,
referenced: referencedKeys.length,
dangling: 0,
deleted: 0,
const deleteStorage =
danglingKeys.length > 0
? this.storageRepo.deleteFiles(
fctx,
danglingKeys,
)
: okAsync(true);
return deleteStorage.andThen(() => {
const deleteRows =
danglingFileIds.length > 0
? this.fileRepo.deleteFiles(
fctx,
danglingFileIds,
userId,
)
: okAsync(true);
return deleteRows.map(() => ({
scanned: existingStorageKeys.length,
referenced: referencedKeys.length,
dangling: danglingKeys.length,
deleted: danglingKeys.length,
deletedRows: danglingFileIds.length,
}));
});
});
}
return this.storageRepo
.deleteFiles(fctx, danglingKeys)
.map(() => ({
scanned: existingStorageKeys.length,
referenced: referencedKeys.length,
dangling: danglingKeys.length,
deleted: danglingKeys.length,
}));
});
}),
}),
),
});
}

View File

@@ -20,7 +20,7 @@ import {
} from "@pkg/db";
import { ResultAsync, errAsync, okAsync } from "neverthrow";
import { FlowExecCtx } from "@core/flow.execution.context";
import { file, fileAccess } from "@pkg/db/schema";
import { file, fileAccess, mobileDevice, mobileMediaAsset } from "@pkg/db/schema";
import { type Err } from "@pkg/result";
import { fileErrors } from "./errors";
import { logDomainEvent } from "@pkg/logger";
@@ -381,7 +381,7 @@ export class FileRepository {
});
}
listReferencedObjectKeysForUser(
listMobileMediaReferencedObjectKeysForUser(
fctx: FlowExecCtx,
userId: string,
): ResultAsync<string[], Err> {
@@ -391,8 +391,13 @@ export class FileRepository {
objectKey: file.objectKey,
metadata: file.metadata,
})
.from(file)
.where(eq(file.userId, userId)),
.from(mobileMediaAsset)
.innerJoin(file, eq(mobileMediaAsset.fileId, file.id))
.innerJoin(
mobileDevice,
eq(mobileMediaAsset.deviceId, mobileDevice.id),
)
.where(eq(mobileDevice.ownerUserId, userId)),
(error) =>
fileErrors.getFilesFailed(
fctx,
@@ -422,6 +427,35 @@ export class FileRepository {
});
}
listMobileMediaDanglingFileIdsForUser(
fctx: FlowExecCtx,
userId: string,
): ResultAsync<string[], Err> {
return ResultAsync.fromPromise(
this.db
.select({ id: file.id })
.from(file)
.where(
and(
eq(file.userId, userId),
sql`NOT EXISTS (
SELECT 1
FROM ${mobileMediaAsset}
INNER JOIN ${mobileDevice}
ON ${mobileMediaAsset.deviceId} = ${mobileDevice.id}
WHERE ${mobileMediaAsset.fileId} = ${file.id}
AND ${mobileDevice.ownerUserId} = ${userId}
)`,
),
),
(error) =>
fileErrors.getFilesFailed(
fctx,
error instanceof Error ? error.message : String(error),
),
).map((rows) => rows.map((row) => row.id));
}
updateFileStatus(
fctx: FlowExecCtx,
fileId: string,