- Add Zod schemas for library titles and all libraries in models/api/library.ts - Create new library router with /all and /titles endpoints - Update onboard router to use namespaced API imports - Export API models as namespace in models/index.ts
150 lines
4.1 KiB
TypeScript
150 lines
4.1 KiB
TypeScript
import { exists, readdir, stat } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { zValidator } from "@hono/zod-validator";
|
|
import { Hono } from "hono";
|
|
import { createHTTPException } from "@/lib/errors";
|
|
import type { ExtendHonoEnv } from "@/lib/types";
|
|
import type { Directory } from "@/models";
|
|
import { API, Path } from "@/models";
|
|
import { OnboardService } from "@/services/onboard";
|
|
|
|
type OnboardRouterEnv = {
|
|
needsOnboarding: boolean;
|
|
};
|
|
|
|
const OnboardRouter = new Hono<ExtendHonoEnv<OnboardRouterEnv>>()
|
|
|
|
.use((c, next) => {
|
|
const service = c.get("service");
|
|
const onboardService = service.get(OnboardService);
|
|
c.set("needsOnboarding", onboardService.getNeedsOnboarding());
|
|
return next();
|
|
})
|
|
.get("/status", (c) => {
|
|
return c.json<API.OnboardStatusOutput>({
|
|
needsOnboarding: c.get("needsOnboarding"),
|
|
});
|
|
})
|
|
.use((c, next) => {
|
|
if (!c.get("needsOnboarding")) {
|
|
throw createHTTPException(403, "You cannot access this resource");
|
|
}
|
|
return next();
|
|
})
|
|
.post("/folder", zValidator("json", Path), async (c) => {
|
|
const body = c.req.valid("json");
|
|
try {
|
|
if (body.path.length === 0) {
|
|
if (os.platform() === "linux") {
|
|
try {
|
|
const drives = await readdir("/", { withFileTypes: true });
|
|
return c.json<Directory[]>(
|
|
drives
|
|
.filter((path) => path.isDirectory())
|
|
.map((value) => ({
|
|
name: `/${value.name}`,
|
|
type: "FOLDER",
|
|
})),
|
|
);
|
|
} catch (_) {
|
|
return c.json<Directory[]>([]);
|
|
}
|
|
}
|
|
}
|
|
// On Linux, ensure path starts with / for absolute paths
|
|
const inputPath =
|
|
os.platform() === "linux"
|
|
? path.join("/", ...body.path)
|
|
: path.join(...body.path);
|
|
if (!(await exists(inputPath))) {
|
|
throw createHTTPException(404, `Path does not exit: ${body.path}`);
|
|
}
|
|
|
|
const stats = await stat(inputPath);
|
|
if (!stats.isDirectory()) {
|
|
throw createHTTPException(400, `Path is not directory: ${inputPath}`);
|
|
}
|
|
|
|
const directories = await readdir(inputPath, { withFileTypes: true });
|
|
const filtered = directories
|
|
.filter((v) => v.isDirectory())
|
|
.map((value): Directory => ({ name: value.name, type: "FOLDER" }));
|
|
return c.json<Directory[]>(filtered);
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
const nodeError = error as NodeJS.ErrnoException;
|
|
|
|
// Access denied / Permission denied
|
|
if (nodeError.code === "EACCES" || nodeError.code === "EPERM") {
|
|
throw createHTTPException(
|
|
500,
|
|
`Access denied: You don't have permission to access this directory`,
|
|
);
|
|
}
|
|
|
|
// Path not found
|
|
if (nodeError.code === "ENOENT") {
|
|
throw createHTTPException(
|
|
500,
|
|
"Directory not found: The specified path does not exist",
|
|
);
|
|
}
|
|
|
|
// Path too long
|
|
if (nodeError.code === "ENAMETOOLONG") {
|
|
throw createHTTPException(500, "Path is too long");
|
|
}
|
|
|
|
// Not a directory
|
|
if (nodeError.code === "ENOTDIR") {
|
|
throw createHTTPException(
|
|
500,
|
|
"Not a directory: The path is not a valid directory",
|
|
);
|
|
}
|
|
|
|
// Too many symbolic links
|
|
if (nodeError.code === "ELOOP") {
|
|
throw createHTTPException(
|
|
500,
|
|
"Too many symbolic links: Unable to resolve the path",
|
|
);
|
|
}
|
|
|
|
// Input/output error
|
|
if (nodeError.code === "EIO") {
|
|
throw createHTTPException(
|
|
500,
|
|
"I/O error: Unable to read from the filesystem",
|
|
);
|
|
}
|
|
|
|
// Re-throw the original error message if it's already formatted
|
|
throw createHTTPException(500, "Failed processing your request");
|
|
}
|
|
|
|
// Unknown error
|
|
throw createHTTPException(
|
|
500,
|
|
`Failed to read directory: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
);
|
|
}
|
|
})
|
|
|
|
.post(
|
|
"/complete",
|
|
zValidator("json", API.OnboardCompletedInput),
|
|
async (c) => {
|
|
const body = c.req.valid("json");
|
|
const service = c.get("service");
|
|
const onboardService = service.get(OnboardService);
|
|
await onboardService.completeOnboarding(body);
|
|
return c.json<API.OnboardCompletedOutput>({
|
|
success: true,
|
|
completed: true,
|
|
});
|
|
},
|
|
);
|
|
export { OnboardRouter };
|