refactor: string params

This commit is contained in:
Nicolas Meienberger 2026-01-31 15:23:12 +01:00
parent e9a2b42d6d
commit f37ae0484b
7 changed files with 24 additions and 93 deletions

View file

@ -497,7 +497,7 @@ export const listFilesInfiniteOptions = (options: Options<ListFilesData>) =>
DefaultError, DefaultError,
InfiniteData<ListFilesResponse>, InfiniteData<ListFilesResponse>,
QueryKey<Options<ListFilesData>>, QueryKey<Options<ListFilesData>>,
number | Pick<QueryKey<Options<ListFilesData>>[0], "body" | "headers" | "path" | "query"> string | Pick<QueryKey<Options<ListFilesData>>[0], "body" | "headers" | "path" | "query">
>( >(
// @ts-ignore // @ts-ignore
{ {
@ -814,7 +814,7 @@ export const listSnapshotFilesInfiniteOptions = (options: Options<ListSnapshotFi
DefaultError, DefaultError,
InfiniteData<ListSnapshotFilesResponse>, InfiniteData<ListSnapshotFilesResponse>,
QueryKey<Options<ListSnapshotFilesData>>, QueryKey<Options<ListSnapshotFilesData>>,
number | Pick<QueryKey<Options<ListSnapshotFilesData>>[0], "body" | "headers" | "path" | "query"> string | Pick<QueryKey<Options<ListSnapshotFilesData>>[0], "body" | "headers" | "path" | "query">
>( >(
// @ts-ignore // @ts-ignore
{ {

View file

@ -682,18 +682,9 @@ export type ListFilesData = {
id: string; id: string;
}; };
query?: { query?: {
/** limit?: string;
* Subdirectory path to list (relative to volume root) offset?: string;
*/
path?: string; path?: string;
/**
* Offset for pagination (default: 0)
*/
offset?: number;
/**
* Maximum number of files to return (default: 100, max: 1000)
*/
limit?: number;
}; };
url: "/api/v1/volumes/{id}/files"; url: "/api/v1/volumes/{id}/files";
}; };
@ -1736,15 +1727,9 @@ export type ListSnapshotFilesData = {
snapshotId: string; snapshotId: string;
}; };
query?: { query?: {
limit?: string;
offset?: string;
path?: string; path?: string;
/**
* Offset for pagination (default: 0)
*/
offset?: number;
/**
* Maximum number of files to return (default: 500, max: 1000)
*/
limit?: number;
}; };
url: "/api/v1/repositories/{id}/snapshots/{snapshotId}/files"; url: "/api/v1/repositories/{id}/snapshots/{snapshotId}/files";
}; };

View file

@ -143,11 +143,10 @@ export const repositoriesController = new Hono()
validator("query", listSnapshotFilesQuery), validator("query", listSnapshotFilesQuery),
async (c) => { async (c) => {
const { id, snapshotId } = c.req.param(); const { id, snapshotId } = c.req.param();
const { path } = c.req.valid("query"); const { path, ...query } = c.req.valid("query");
const decodedPath = path ? decodeURIComponent(path) : undefined; const decodedPath = path ? decodeURIComponent(path) : undefined;
const query = c.req.query();
const offset = Math.max(0, Number.parseInt(query.offset ?? "0", 10) || 0); const offset = Math.max(0, Number.parseInt(query.offset ?? "0", 10) || 0);
const limit = Math.min(1000, Math.max(1, Number.parseInt(query.limit ?? "500", 10) || 500)); const limit = Math.min(1000, Math.max(1, Number.parseInt(query.limit ?? "500", 10) || 500));

View file

@ -262,43 +262,14 @@ export type ListSnapshotFilesDto = typeof listSnapshotFilesResponse.infer;
export const listSnapshotFilesQuery = type({ export const listSnapshotFilesQuery = type({
path: "string?", path: "string?",
offset: "string.integer?",
limit: "string.integer?",
}); });
export const listSnapshotFilesDto = describeRoute({ export const listSnapshotFilesDto = describeRoute({
description: "List files and directories in a snapshot", description: "List files and directories in a snapshot",
tags: ["Repositories"], tags: ["Repositories"],
operationId: "listSnapshotFiles", operationId: "listSnapshotFiles",
parameters: [
{
in: "query",
name: "path",
required: false,
schema: {
type: "string",
},
description: "Subdirectory path to list",
},
{
in: "query",
name: "offset",
required: false,
schema: {
type: "integer",
default: 0,
},
description: "Offset for pagination (default: 0)",
},
{
in: "query",
name: "limit",
required: false,
schema: {
type: "integer",
default: 500,
},
description: "Maximum number of files to return (default: 500, max: 1000)",
},
],
responses: { responses: {
200: { 200: {
description: "List of files and directories in the snapshot", description: "List of files and directories in the snapshot",

View file

@ -21,6 +21,7 @@ import {
type ListFilesDto, type ListFilesDto,
browseFilesystemDto, browseFilesystemDto,
type BrowseFilesystemDto, type BrowseFilesystemDto,
listFilesQuery,
} from "./volume.dto"; } from "./volume.dto";
import { volumeService } from "./volume.service"; import { volumeService } from "./volume.service";
import { getVolumePath } from "./helpers"; import { getVolumePath } from "./helpers";
@ -104,15 +105,14 @@ export const volumeController = new Hono()
return c.json({ error, status }, 200); return c.json({ error, status }, 200);
}) })
.get("/:id/files", listFilesDto, async (c) => { .get("/:id/files", validator("query", listFilesQuery), listFilesDto, async (c) => {
const { id } = c.req.param(); const { id } = c.req.param();
const subPath = c.req.query("path"); const { path, ...query } = c.req.valid("query");
const offsetParam = c.req.query("offset");
const offset = offsetParam ? parseInt(offsetParam, 10) : 0;
const limitParam = c.req.query("limit");
const limit = limitParam ? parseInt(limitParam, 10) : undefined;
const result = await volumeService.listFiles(id, subPath, offset, limit); const offset = Math.max(0, Number.parseInt(query.offset ?? "0", 10) || 0);
const limit = Math.min(1000, Math.max(1, Number.parseInt(query.limit ?? "500", 10) || 500));
const result = await volumeService.listFiles(id, path, offset, limit);
const response = { const response = {
files: result.files, files: result.files,

View file

@ -283,41 +283,16 @@ export const listFilesResponse = type({
}); });
export type ListFilesDto = typeof listFilesResponse.infer; export type ListFilesDto = typeof listFilesResponse.infer;
export const listFilesQuery = type({
path: "string?",
offset: "string.integer?",
limit: "string.integer?",
});
export const listFilesDto = describeRoute({ export const listFilesDto = describeRoute({
description: "List files in a volume directory", description: "List files in a volume directory",
operationId: "listFiles", operationId: "listFiles",
tags: ["Volumes"], tags: ["Volumes"],
parameters: [
{
in: "query",
name: "path",
required: false,
schema: {
type: "string",
},
description: "Subdirectory path to list (relative to volume root)",
},
{
in: "query",
name: "offset",
required: false,
schema: {
type: "integer",
default: 0,
},
description: "Offset for pagination (default: 0)",
},
{
in: "query",
name: "limit",
required: false,
schema: {
type: "integer",
default: 100,
},
description: "Maximum number of files to return (default: 100, max: 1000)",
},
],
responses: { responses: {
200: { 200: {
description: "List of files in the volume", description: "List of files in the volume",

View file

@ -18,6 +18,7 @@ import { serverEvents } from "../../core/events";
import { volumeConfigSchema, type BackendConfig } from "~/schemas/volumes"; import { volumeConfigSchema, type BackendConfig } from "~/schemas/volumes";
import { type } from "arktype"; import { type } from "arktype";
import { getOrganizationId } from "~/server/core/request-context"; import { getOrganizationId } from "~/server/core/request-context";
import { isNodeJSErrnoException } from "~/server/utils/fs";
async function encryptSensitiveFields(config: BackendConfig): Promise<BackendConfig> { async function encryptSensitiveFields(config: BackendConfig): Promise<BackendConfig> {
switch (config.backend) { switch (config.backend) {
@ -373,7 +374,7 @@ const listFiles = async (
hasMore: startOffset + entries.length < total, hasMore: startOffset + entries.length < total,
}; };
} catch (error) { } catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") { if (isNodeJSErrnoException(error) && error.code === "ENOENT") {
throw new NotFoundError("Directory not found"); throw new NotFoundError("Directory not found");
} }
throw new InternalServerError(`Failed to list files: ${toMessage(error)}`); throw new InternalServerError(`Failed to list files: ${toMessage(error)}`);