Skip to content

Commit 7b59bba

Browse files
committed
refactor: put export endpoint in system controller
1 parent 081a10a commit 7b59bba

8 files changed

Lines changed: 198 additions & 227 deletions

File tree

app/client/api-client/sdk.gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ export const downloadResticPassword = <ThrowOnError extends boolean = false>(opt
430430
* Export full configuration including all volumes, repositories, backup schedules, and notifications
431431
*/
432432
export const exportFullConfig = <ThrowOnError extends boolean = false>(options?: Options<ExportFullConfigData, ThrowOnError>) => (options?.client ?? client).post<ExportFullConfigResponses, ExportFullConfigErrors, ThrowOnError>({
433-
url: '/api/v1/config/export',
433+
url: '/api/v1/system/export',
434434
...options,
435435
headers: {
436436
'Content-Type': 'application/json',

app/client/api-client/types.gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3345,7 +3345,7 @@ export type ExportFullConfigData = {
33453345
};
33463346
path?: never;
33473347
query?: never;
3348-
url: '/api/v1/config/export';
3348+
url: '/api/v1/system/export';
33493349
};
33503350

33513351
export type ExportFullConfigErrors = {

app/server/app.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { volumeController } from "./modules/volumes/volume.controller";
1212
import { backupScheduleController } from "./modules/backups/backups.controller";
1313
import { eventsController } from "./modules/events/events.controller";
1414
import { notificationsController } from "./modules/notifications/notifications.controller";
15-
import { configExportController } from "./modules/lifecycle/config-export.controller";
1615
import { handleServiceError } from "./utils/errors";
1716
import { logger } from "./utils/logger";
1817
import { config } from "./core/config";
@@ -61,8 +60,7 @@ export const createApp = () => {
6160
.route("/api/v1/backups", backupScheduleController)
6261
.route("/api/v1/notifications", notificationsController)
6362
.route("/api/v1/system", systemController)
64-
.route("/api/v1/events", eventsController)
65-
.route("/api/v1/config", configExportController);
63+
.route("/api/v1/events", eventsController);
6664

6765
app.get("/api/v1/openapi.json", generalDescriptor(app));
6866
app.get("/api/v1/docs", requireAuth, scalarDescriptor);

app/server/modules/lifecycle/config-export.controller.ts

Lines changed: 0 additions & 155 deletions
This file was deleted.

app/server/modules/lifecycle/config-export.dto.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

app/server/modules/system/system.controller.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { validator } from "hono-openapi";
33
import {
44
downloadResticPasswordBodySchema,
55
downloadResticPasswordDto,
6+
fullExportBodySchema,
7+
fullExportDto,
68
getUpdatesDto,
79
systemInfoDto,
810
type SystemInfoDto,
@@ -61,4 +63,20 @@ export const systemController = new Hono()
6163
return c.json({ message: "Failed to read Restic password file" }, 500);
6264
}
6365
},
64-
);
66+
)
67+
.post("/export", fullExportDto, validator("json", fullExportBodySchema), async (c) => {
68+
const user = c.get("user");
69+
const body = c.req.valid("json");
70+
71+
const [dbUser] = await db.select().from(usersTable).where(eq(usersTable.id, user.id));
72+
73+
if (!dbUser) {
74+
return c.json({ message: "User not found" }, 401);
75+
}
76+
77+
const isValid = await Bun.password.verify(body.password, dbUser.passwordHash);
78+
79+
if (!isValid) {
80+
return c.json({ message: "Incorrect password" }, 401);
81+
}
82+
});

app/server/modules/system/system.dto.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,66 @@ export const downloadResticPasswordDto = describeRoute({
7979
},
8080
},
8181
});
82+
83+
export const fullExportBodySchema = type({
84+
includeMetadata: "boolean = false",
85+
password: "string",
86+
});
87+
88+
export type FullExportBody = typeof fullExportBodySchema.infer;
89+
90+
const exportResponseSchema = type({
91+
version: "number",
92+
exportedAt: "string?",
93+
recoveryKey: "string?",
94+
volumes: "unknown[]?",
95+
repositories: "unknown[]?",
96+
backupSchedules: "unknown[]?",
97+
notificationDestinations: "unknown[]?",
98+
users: type({
99+
id: "number?",
100+
username: "string",
101+
passwordHash: "string?",
102+
createdAt: "number?",
103+
updatedAt: "number?",
104+
hasDownloadedResticPassword: "boolean?",
105+
})
106+
.array()
107+
.optional(),
108+
});
109+
110+
const errorResponseSchema = type({
111+
error: "string",
112+
});
113+
114+
export const fullExportDto = describeRoute({
115+
description: "Export full configuration including all volumes, repositories, backup schedules, and notifications",
116+
operationId: "exportFullConfig",
117+
tags: ["Config Export"],
118+
responses: {
119+
200: {
120+
description: "Full configuration export",
121+
content: {
122+
"application/json": {
123+
schema: resolver(exportResponseSchema),
124+
},
125+
},
126+
},
127+
401: {
128+
description: "Password required for export or authentication failed",
129+
content: {
130+
"application/json": {
131+
schema: resolver(errorResponseSchema),
132+
},
133+
},
134+
},
135+
500: {
136+
description: "Export failed",
137+
content: {
138+
"application/json": {
139+
schema: resolver(errorResponseSchema),
140+
},
141+
},
142+
},
143+
},
144+
});

0 commit comments

Comments
 (0)