refactor: local repo path concat as a code migration

This commit is contained in:
Nicolas Meienberger 2026-02-14 00:04:06 +01:00
parent 9a68d1039f
commit c1c8138197
4 changed files with 99 additions and 2178 deletions

View file

@ -1,23 +0,0 @@
UPDATE `repositories_table`
SET
`config` = json_set(
`config`,
'$.path',
CASE
WHEN json_extract(`config`, '$.path') IS NULL OR trim(json_extract(`config`, '$.path')) = ''
THEN '/var/lib/zerobyte/repositories/' || json_extract(`config`, '$.name')
ELSE rtrim(json_extract(`config`, '$.path'), '/') || '/' || json_extract(`config`, '$.name')
END
),
`updated_at` = (unixepoch() * 1000)
WHERE `type` = 'local'
AND json_extract(`config`, '$.name') IS NOT NULL
AND trim(json_extract(`config`, '$.name')) <> ''
AND (
json_extract(`config`, '$.path') IS NULL
OR trim(json_extract(`config`, '$.path')) = ''
OR substr(
rtrim(json_extract(`config`, '$.path'), '/'),
-length('/' || json_extract(`config`, '$.name'))
) != '/' || json_extract(`config`, '$.name')
);

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@ import { logger } from "../../utils/logger";
import { v00001 } from "./migrations/00001-retag-snapshots";
import { v00002 } from "./migrations/00002-isolate-restic-passwords";
import { v00003 } from "./migrations/00003-assign-organization";
import { v00004 } from "./migrations/00004-concat-path-name";
import { sql } from "drizzle-orm";
import { appMetadataTable, usersTable } from "../../db/schema";
import { db } from "../../db/db";
@ -36,7 +37,7 @@ type MigrationEntity = {
dependsOn?: string[];
};
const registry: MigrationEntity[] = [v00001, v00002, v00003];
const registry: MigrationEntity[] = [v00001, v00002, v00003, v00004];
export const runMigrations = async () => {
const userCount = await db.select({ count: sql<number>`count(*)` }).from(usersTable);

View file

@ -0,0 +1,97 @@
import { and, eq } from "drizzle-orm";
import { db } from "../../../db/db";
import { repositoriesTable } from "../../../db/schema";
import { logger } from "../../../utils/logger";
import { toMessage } from "~/server/utils/errors";
const DEFAULT_LOCAL_REPOSITORY_ROOT = "/var/lib/zerobyte/repositories";
type MigrationError = { name: string; error: string };
const asString = (value: unknown): string | null => {
if (typeof value !== "string") {
return null;
}
return value;
};
const hasValue = (value: string | null): value is string => {
return value !== null && value.trim() !== "";
};
const trimTrailingSlashes = (value: string): string => {
return value.replace(/\/+$/, "");
};
const isPathAlreadyMigrated = (path: string, name: string): boolean => {
const normalizedPath = trimTrailingSlashes(path);
return normalizedPath.endsWith(`/${name}`);
};
const buildPath = (path: string | null, name: string): string => {
if (path === null || path.trim() === "") {
return `${DEFAULT_LOCAL_REPOSITORY_ROOT}/${name}`;
}
return `${trimTrailingSlashes(path)}/${name}`;
};
const execute = async () => {
const errors: MigrationError[] = [];
const localRepositories = await db.query.repositoriesTable.findMany({ where: { type: "local" } });
let migratedCount = 0;
for (const repository of localRepositories) {
try {
const configValue = repository.config as unknown;
if (typeof configValue !== "object" || configValue === null || Array.isArray(configValue)) {
errors.push({
name: `repository:${repository.id}`,
error: "Repository config is not a valid JSON object",
});
continue;
}
const config = { ...(configValue as Record<string, unknown>) };
const localRepositoryName = asString(config.name);
if (!hasValue(localRepositoryName)) {
continue;
}
const currentPath = asString(config.path);
if (hasValue(currentPath) && isPathAlreadyMigrated(currentPath, localRepositoryName)) {
continue;
}
config.path = buildPath(currentPath, localRepositoryName);
await db
.update(repositoriesTable)
.set({
config: config as typeof repository.config,
updatedAt: Date.now(),
})
.where(and(eq(repositoriesTable.id, repository.id), eq(repositoriesTable.type, "local")));
migratedCount += 1;
} catch (err) {
errors.push({
name: `repository:${repository.id}`,
error: toMessage(err),
});
}
}
logger.info(`Migration 00004-concat-path-name updated ${migratedCount} local repositories.`);
return { success: errors.length === 0, errors };
};
export const v00004 = {
execute,
id: "00004-concat-path-name",
type: "maintenance" as const,
};