Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions app/server/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
} from "~/schemas/restic";
import type { BackendStatus, BackendType, volumeConfigSchema } from "~/schemas/volumes";
import type { NotificationType, notificationConfigSchema } from "~/schemas/notifications";
import type { ShortId } from "~/server/utils/branded";

/**
* Users Table
Expand Down Expand Up @@ -179,7 +180,7 @@ export const volumesTable = sqliteTable(
"volumes_table",
{
id: int().primaryKey({ autoIncrement: true }),
shortId: text("short_id").notNull().unique(),
shortId: text("short_id").$type<ShortId>().notNull().unique(),
name: text().notNull(),
type: text().$type<BackendType>().notNull(),
status: text().$type<BackendStatus>().notNull().default("unmounted"),
Expand Down Expand Up @@ -210,7 +211,7 @@ export type VolumeInsert = typeof volumesTable.$inferInsert;
*/
export const repositoriesTable = sqliteTable("repositories_table", {
id: text().primaryKey(),
shortId: text("short_id").notNull().unique(),
shortId: text("short_id").$type<ShortId>().notNull().unique(),
name: text().notNull(),
type: text().$type<RepositoryBackend>().notNull(),
config: text("config", { mode: "json" }).$type<typeof repositoryConfigSchema.inferOut>().notNull(),
Expand Down Expand Up @@ -243,7 +244,7 @@ export type RepositoryInsert = typeof repositoriesTable.$inferInsert;
*/
export const backupSchedulesTable = sqliteTable("backup_schedules_table", {
id: int().primaryKey({ autoIncrement: true }),
shortId: text("short_id").notNull().unique(),
shortId: text("short_id").$type<ShortId>().notNull().unique(),
name: text().notNull(),
volumeId: int("volume_id")
.notNull()
Expand Down
2 changes: 1 addition & 1 deletion app/server/jobs/auto-remount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class VolumeAutoRemountJob extends Job {
if (volume.autoRemount) {
try {
await withContext({ organizationId: volume.organizationId }, async () => {
await volumeService.mountVolume(volume.id);
await volumeService.mountVolume(volume.shortId);
});
} catch (err) {
logger.error(`Failed to auto-remount volume ${volume.name}:`, err);
Expand Down
16 changes: 10 additions & 6 deletions app/server/jobs/healthchecks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ export class VolumeHealthCheckJob extends Job {
});

for (const volume of volumes) {
await withContext({ organizationId: volume.organizationId }, async () => {
const { status } = await volumeService.checkHealth(volume.id);
if (status === "error" && volume.autoRemount) {
await volumeService.mountVolume(volume.id);
}
});
try {
await withContext({ organizationId: volume.organizationId }, async () => {
const { status } = await volumeService.checkHealth(volume.shortId);
if (status === "error" && volume.autoRemount) {
await volumeService.mountVolume(volume.shortId);
}
});
} catch (error) {
logger.error(`Health check failed for volume ${volume.name}:`, error);
}
}

return { done: true, timestamp: new Date() };
Expand Down
2 changes: 1 addition & 1 deletion app/server/jobs/repository-healthchecks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class RepositoryHealthCheckJob extends Job {
for (const repository of repositories) {
try {
await withContext({ organizationId: repository.organizationId }, async () => {
await repositoriesService.checkHealth(repository.id);
await repositoriesService.checkHealth(repository.shortId);
});
} catch (error) {
logger.error(`Health check failed for repository ${repository.name}:`, error);
Expand Down
27 changes: 14 additions & 13 deletions app/server/modules/backups/backups.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { notificationsService } from "../notifications/notifications.service";
import { requireAuth } from "../auth/auth.middleware";
import { backupsExecutionService } from "./backups.execution";
import { logger } from "~/server/utils/logger";
import { asShortId } from "~/server/utils/branded";

export const backupScheduleController = new Hono()
.use(requireAuth)
Expand All @@ -53,13 +54,13 @@ export const backupScheduleController = new Hono()
return c.json<ListBackupSchedulesResponseDto>(schedules, 200);
})
.get("/:shortId", getBackupScheduleDto, async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const schedule = await backupsService.getScheduleByShortId(shortId);

return c.json<GetBackupScheduleDto>(schedule, 200);
})
.get("/volume/:volumeShortId", getBackupScheduleForVolumeDto, async (c) => {
const volumeShortId = c.req.param("volumeShortId");
const volumeShortId = asShortId(c.req.param("volumeShortId"));
const schedule = await backupsService.getScheduleForVolume(volumeShortId);

return c.json<GetBackupScheduleForVolumeResponseDto>(schedule, 200);
Expand All @@ -71,20 +72,20 @@ export const backupScheduleController = new Hono()
return c.json<CreateBackupScheduleDto>(schedule, 201);
})
.patch("/:shortId", updateBackupScheduleDto, validator("json", updateBackupScheduleBody), async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const body = c.req.valid("json");
const schedule = await backupsService.updateSchedule(shortId, body);

return c.json<UpdateBackupScheduleDto>(schedule, 200);
})
.delete("/:shortId", deleteBackupScheduleDto, async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
await backupsService.deleteSchedule(shortId);

return c.json<DeleteBackupScheduleDto>({ success: true }, 200);
})
.post("/:shortId/run", runBackupNowDto, async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const schedule = await backupsService.getScheduleByShortId(shortId);
const result = await backupsExecutionService.validateBackupExecution(schedule.id, true);

Expand All @@ -103,21 +104,21 @@ export const backupScheduleController = new Hono()
return c.json<RunBackupNowDto>({ success: true }, 200);
})
.post("/:shortId/stop", stopBackupDto, async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const schedule = await backupsService.getScheduleByShortId(shortId);
await backupsExecutionService.stopBackup(schedule.id);

return c.json<StopBackupDto>({ success: true }, 200);
})
.post("/:shortId/forget", runForgetDto, async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const schedule = await backupsService.getScheduleByShortId(shortId);
await backupsExecutionService.runForget(schedule.id);

return c.json<RunForgetDto>({ success: true }, 200);
})
.get("/:shortId/notifications", getScheduleNotificationsDto, async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const schedule = await backupsService.getScheduleByShortId(shortId);
const assignments = await notificationsService.getScheduleNotifications(schedule.id);

Expand All @@ -128,7 +129,7 @@ export const backupScheduleController = new Hono()
updateScheduleNotificationsDto,
validator("json", updateScheduleNotificationsBody),
async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const schedule = await backupsService.getScheduleByShortId(shortId);
const body = c.req.valid("json");
const assignments = await notificationsService.updateScheduleNotifications(schedule.id, body.assignments);
Expand All @@ -137,27 +138,27 @@ export const backupScheduleController = new Hono()
},
)
.get("/:shortId/mirrors", getScheduleMirrorsDto, async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const mirrors = await backupsService.getMirrors(shortId);

return c.json<GetScheduleMirrorsDto>(mirrors, 200);
})
.put("/:shortId/mirrors", updateScheduleMirrorsDto, validator("json", updateScheduleMirrorsBody), async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const body = c.req.valid("json");
const mirrors = await backupsService.updateMirrors(shortId, body);

return c.json<UpdateScheduleMirrorsDto>(mirrors, 200);
})
.get("/:shortId/mirrors/compatibility", getMirrorCompatibilityDto, async (c) => {
const shortId = c.req.param("shortId");
const shortId = asShortId(c.req.param("shortId"));
const compatibility = await backupsService.getMirrorCompatibility(shortId);

return c.json<GetMirrorCompatibilityDto>(compatibility, 200);
})
.post("/reorder", reorderBackupSchedulesDto, validator("json", reorderBackupSchedulesBody), async (c) => {
const body = c.req.valid("json");
await backupsService.reorderSchedules(body.scheduleShortIds);
await backupsService.reorderSchedules(body.scheduleShortIds.map(asShortId));

return c.json<ReorderBackupSchedulesDto>({ success: true }, 200);
});
41 changes: 27 additions & 14 deletions app/server/modules/backups/backups.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { checkMirrorCompatibility, getIncompatibleMirrorError } from "~/server/u
import { generateShortId } from "~/server/utils/id";
import { getOrganizationId } from "~/server/core/request-context";
import { calculateNextRun } from "./backup.helpers";
import { asShortId, type ShortId } from "~/server/utils/branded";

const listSchedules = async () => {
const organizationId = getOrganizationId();
Expand Down Expand Up @@ -38,10 +39,10 @@ const getScheduleById = async (scheduleId: number) => {
return schedule;
};

const getScheduleByShortId = async (shortId: string) => {
const getScheduleByShortId = async (shortId: ShortId) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: { AND: [{ shortId }, { organizationId }] },
where: { AND: [{ shortId: { eq: shortId } }, { organizationId }] },
with: { volume: true, repository: true },
});

Expand All @@ -60,7 +61,10 @@ const getScheduleByIdOrShortId = async (idOrShortId: string | number) => {
const organizationId = getOrganizationId();
const schedule = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [{ OR: [{ id: Number(idOrShortId) }, { shortId: String(idOrShortId) }] }, { organizationId }],
AND: [
{ OR: [{ id: Number(idOrShortId) }, { shortId: { eq: asShortId(String(idOrShortId)) } }] },
{ organizationId },
],
},
with: { volume: true, repository: true },
});
Expand Down Expand Up @@ -94,7 +98,10 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {

const volume = await db.query.volumesTable.findFirst({
where: {
AND: [{ OR: [{ id: Number(data.volumeId) }, { shortId: String(data.volumeId) }] }, { organizationId }],
AND: [
{ OR: [{ id: Number(data.volumeId) }, { shortId: { eq: asShortId(String(data.volumeId)) } }] },
{ organizationId },
],
},
});

Expand All @@ -104,7 +111,7 @@ const createSchedule = async (data: CreateBackupScheduleBody) => {

const repository = await db.query.repositoriesTable.findFirst({
where: {
AND: [{ OR: [{ id: data.repositoryId }, { shortId: data.repositoryId }] }, { organizationId }],
AND: [{ OR: [{ id: data.repositoryId }, { shortId: { eq: asShortId(data.repositoryId) } }] }, { organizationId }],
},
});

Expand Down Expand Up @@ -145,7 +152,7 @@ const updateSchedule = async (scheduleIdOrShortId: number | string, data: Update
const schedule = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] },
{ organizationId },
],
},
Expand Down Expand Up @@ -173,7 +180,7 @@ const updateSchedule = async (scheduleIdOrShortId: number | string, data: Update

const repository = await db.query.repositoriesTable.findFirst({
where: {
AND: [{ OR: [{ id: data.repositoryId }, { shortId: data.repositoryId }] }, { organizationId }],
AND: [{ OR: [{ id: data.repositoryId }, { shortId: { eq: asShortId(data.repositoryId) } }] }, { organizationId }],
},
});

Expand Down Expand Up @@ -202,7 +209,7 @@ const deleteSchedule = async (scheduleIdOrShortId: number | string) => {
const schedule = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] },
{ organizationId },
],
},
Expand All @@ -221,7 +228,10 @@ const getScheduleForVolume = async (volumeIdOrShortId: number | string) => {
const organizationId = getOrganizationId();
const volume = await db.query.volumesTable.findFirst({
where: {
AND: [{ OR: [{ id: Number(volumeIdOrShortId) }, { shortId: String(volumeIdOrShortId) }] }, { organizationId }],
AND: [
{ OR: [{ id: Number(volumeIdOrShortId) }, { shortId: { eq: asShortId(String(volumeIdOrShortId)) } }] },
{ organizationId },
],
},
columns: { id: true },
});
Expand Down Expand Up @@ -249,7 +259,7 @@ const getMirrors = async (scheduleIdOrShortId: number | string) => {
const schedule = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] },
{ organizationId },
],
},
Expand Down Expand Up @@ -284,7 +294,7 @@ const updateMirrors = async (scheduleIdOrShortId: number | string, data: UpdateS
const schedule = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] },
{ organizationId },
],
},
Expand All @@ -299,7 +309,10 @@ const updateMirrors = async (scheduleIdOrShortId: number | string, data: UpdateS
data.mirrors.map(async (mirror) => {
const repo = await db.query.repositoriesTable.findFirst({
where: {
AND: [{ OR: [{ id: mirror.repositoryId }, { shortId: mirror.repositoryId }] }, { organizationId }],
AND: [
{ OR: [{ id: mirror.repositoryId }, { shortId: { eq: asShortId(mirror.repositoryId) } }] },
{ organizationId },
],
},
});

Expand Down Expand Up @@ -360,7 +373,7 @@ const getMirrorCompatibility = async (scheduleIdOrShortId: number | string) => {
const schedule = await db.query.backupSchedulesTable.findFirst({
where: {
AND: [
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: String(scheduleIdOrShortId) }] },
{ OR: [{ id: Number(scheduleIdOrShortId) }, { shortId: { eq: asShortId(String(scheduleIdOrShortId)) } }] },
{ organizationId },
],
},
Expand All @@ -381,7 +394,7 @@ const getMirrorCompatibility = async (scheduleIdOrShortId: number | string) => {
return compatibility;
};

const reorderSchedules = async (scheduleShortIds: string[]) => {
const reorderSchedules = async (scheduleShortIds: ShortId[]) => {
const organizationId = getOrganizationId();
const uniqueIds = new Set(scheduleShortIds);
if (uniqueIds.size !== scheduleShortIds.length) {
Expand Down
6 changes: 3 additions & 3 deletions app/server/modules/lifecycle/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const ensureLatestConfigurationSchema = async () => {

for (const volume of volumes) {
await withContext({ organizationId: volume.organizationId }, async () => {
await volumeService.updateVolume(volume.id, volume).catch((err) => {
await volumeService.updateVolume(volume.shortId, volume).catch((err) => {
logger.error(`Failed to update volume ${volume.name}: ${err}`);
});
});
Expand All @@ -30,7 +30,7 @@ const ensureLatestConfigurationSchema = async () => {

for (const repo of repositories) {
await withContext({ organizationId: repo.organizationId }, async () => {
await repositoriesService.updateRepository(repo.id, {}).catch((err) => {
await repositoriesService.updateRepository(repo.shortId, {}).catch((err) => {
logger.error(`Failed to update repository ${repo.name}: ${err}`);
});
});
Expand Down Expand Up @@ -77,7 +77,7 @@ export const startup = async () => {

for (const volume of volumes) {
await withContext({ organizationId: volume.organizationId }, async () => {
await volumeService.mountVolume(volume.id).catch((err) => {
await volumeService.mountVolume(volume.shortId).catch((err) => {
logger.error(`Error auto-remounting volume ${volume.name} on startup: ${err.message}`);
});
});
Expand Down
Loading