From 110ebfd16040fdb70b7913b14a20897cfc052a2c Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Sun, 28 Sep 2025 18:14:48 +0200 Subject: [PATCH] fix: form reset and default values --- apps/client/app/api-client/client.gen.ts | 2 +- apps/client/app/api-client/types.gen.ts | 44 +++++------ .../app/components/create-volume-form.tsx | 78 +++++++++++++------ .../app/modules/details/tabs/docker.tsx | 11 ++- apps/server/package.json | 3 +- apps/server/src/index.ts | 6 +- .../src/modules/backends/nfs/nfs-backend.ts | 13 ++-- .../src/modules/volumes/volume.controller.ts | 2 +- apps/server/src/modules/volumes/volume.dto.ts | 23 ++---- apps/server/tsconfig.json | 5 +- bun.lock | 27 +++---- openapi-ts.config.ts | 2 +- packages/schemas/src/index.ts | 19 +++-- 13 files changed, 133 insertions(+), 102 deletions(-) diff --git a/apps/client/app/api-client/client.gen.ts b/apps/client/app/api-client/client.gen.ts index 88fa974..c467a6f 100644 --- a/apps/client/app/api-client/client.gen.ts +++ b/apps/client/app/api-client/client.gen.ts @@ -17,6 +17,6 @@ export type CreateClientConfig = export const client = createClient( createConfig({ - baseUrl: "http://localhost:3000", + baseUrl: "http://localhost:4096", }), ); diff --git a/apps/client/app/api-client/types.gen.ts b/apps/client/app/api-client/types.gen.ts index 6e7b018..e4ee4a2 100644 --- a/apps/client/app/api-client/types.gen.ts +++ b/apps/client/app/api-client/types.gen.ts @@ -23,7 +23,7 @@ export type ListVolumesResponses = { exportPath: string; server: string; version: "3" | "4" | "4.1"; - port?: number | string; + port?: number; } | { backend: "smb"; @@ -32,20 +32,20 @@ export type ListVolumesResponses = { share: string; username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0"; - port?: number | string; + port?: number; domain?: string; } | { backend: "webdav"; path: string; server: string; - port?: number | string; + port?: number; password?: string; ssl?: boolean; username?: string; }; createdAt: number; - lastError: string; + lastError: string | null; lastHealthCheck: number; name: string; path: string; @@ -69,7 +69,7 @@ export type CreateVolumeData = { exportPath: string; server: string; version: "3" | "4" | "4.1"; - port?: number | string; + port?: number; } | { backend: "smb"; @@ -78,14 +78,14 @@ export type CreateVolumeData = { share: string; username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0"; - port?: number | string; + port?: number; domain?: string; } | { backend: "webdav"; path: string; server: string; - port?: number | string; + port?: number; password?: string; ssl?: boolean; username?: string; @@ -123,7 +123,7 @@ export type TestConnectionData = { exportPath: string; server: string; version: "3" | "4" | "4.1"; - port?: number | string; + port?: number; } | { backend: "smb"; @@ -132,14 +132,14 @@ export type TestConnectionData = { share: string; username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0"; - port?: number | string; + port?: number; domain?: string; } | { backend: "webdav"; path: string; server: string; - port?: number | string; + port?: number; password?: string; ssl?: boolean; username?: string; @@ -219,7 +219,7 @@ export type GetVolumeResponses = { exportPath: string; server: string; version: "3" | "4" | "4.1"; - port?: number | string; + port?: number; } | { backend: "smb"; @@ -228,20 +228,20 @@ export type GetVolumeResponses = { share: string; username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0"; - port?: number | string; + port?: number; domain?: string; } | { backend: "webdav"; path: string; server: string; - port?: number | string; + port?: number; password?: string; ssl?: boolean; username?: string; }; createdAt: number; - lastError: string; + lastError: string | null; lastHealthCheck: number; name: string; path: string; @@ -266,7 +266,7 @@ export type UpdateVolumeData = { exportPath: string; server: string; version: "3" | "4" | "4.1"; - port?: number | string; + port?: number; } | { backend: "smb"; @@ -275,14 +275,14 @@ export type UpdateVolumeData = { share: string; username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0"; - port?: number | string; + port?: number; domain?: string; } | { backend: "webdav"; path: string; server: string; - port?: number | string; + port?: number; password?: string; ssl?: boolean; username?: string; @@ -319,7 +319,7 @@ export type UpdateVolumeResponses = { exportPath: string; server: string; version: "3" | "4" | "4.1"; - port?: number | string; + port?: number; } | { backend: "smb"; @@ -328,20 +328,20 @@ export type UpdateVolumeResponses = { share: string; username: string; vers?: "1.0" | "2.0" | "2.1" | "3.0"; - port?: number | string; + port?: number; domain?: string; } | { backend: "webdav"; path: string; server: string; - port?: number | string; + port?: number; password?: string; ssl?: boolean; username?: string; }; createdAt: number; - lastError: string; + lastError: string | null; lastHealthCheck: number; name: string; path: string; @@ -472,5 +472,5 @@ export type HealthCheckVolumeResponses = { export type HealthCheckVolumeResponse = HealthCheckVolumeResponses[keyof HealthCheckVolumeResponses]; export type ClientOptions = { - baseUrl: "http://localhost:3000" | (string & {}); + baseUrl: "http://localhost:4096" | (string & {}); }; diff --git a/apps/client/app/components/create-volume-form.tsx b/apps/client/app/components/create-volume-form.tsx index 815a1bc..700edc2 100644 --- a/apps/client/app/components/create-volume-form.tsx +++ b/apps/client/app/components/create-volume-form.tsx @@ -3,7 +3,7 @@ import { volumeConfigSchema } from "@ironmount/schemas"; import { useMutation } from "@tanstack/react-query"; import { type } from "arktype"; import { CheckCircle, Loader2, XCircle } from "lucide-react"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { testConnectionMutation } from "~/api-client/@tanstack/react-query.gen"; import { slugify } from "~/lib/utils"; @@ -26,15 +26,31 @@ type Props = { loading?: boolean; }; +const defaultValuesForType = { + directory: { backend: "directory" as const }, + nfs: { backend: "nfs" as const, port: 2049, version: "4.1" as const }, + smb: { backend: "smb" as const, port: 445, vers: "3.0" as const }, + webdav: { backend: "webdav" as const, port: 80, ssl: false }, +}; + export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, formId, loading }: Props) => { const form = useForm({ resolver: arktypeResolver(formSchema), defaultValues: initialValues, + resetOptions: { + keepDefaultValues: true, + keepDirtyValues: false, + }, }); const { watch, getValues } = form; const watchedBackend = watch("backend"); + const watchedName = watch("name"); + + useEffect(() => { + form.reset({ name: watchedName, ...defaultValuesForType[watchedBackend as keyof typeof defaultValuesForType] }); + }, [watchedBackend, watchedName, form.reset]); const [testStatus, setTestStatus] = useState<"idle" | "loading" | "success" | "error">("idle"); const [testMessage, setTestMessage] = useState(""); @@ -74,14 +90,15 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
( Name field.onChange(slugify(e.target.value))} max={32} min={1} @@ -95,6 +112,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( @@ -121,12 +139,13 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for {watchedBackend === "nfs" && ( <> ( Server - + NFS server IP address or hostname. @@ -134,12 +153,13 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Export Path - + Path to the NFS export on the server. @@ -147,17 +167,14 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Port - field.onChange(parseInt(e.target.value, 10) || undefined)} - /> + NFS server port (default: 2049). @@ -165,11 +182,13 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Version - @@ -192,12 +211,13 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for {watchedBackend === "webdav" && ( <> ( Server - + WebDAV server hostname or IP address. @@ -205,12 +225,13 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Path - + Path to the WebDAV directory on the server. @@ -218,12 +239,13 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Username (Optional) - + Username for WebDAV authentication (optional). @@ -231,12 +253,13 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Password (Optional) - + Password for WebDAV authentication (optional). @@ -244,17 +267,14 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Port - field.onChange(parseInt(e.target.value, 10) || undefined)} - /> + WebDAV server port (default: 80 for HTTP, 443 for HTTPS). @@ -262,7 +282,9 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Use SSL/HTTPS @@ -288,6 +310,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for {watchedBackend === "smb" && ( <> ( @@ -301,6 +324,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( @@ -314,6 +338,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( @@ -327,6 +352,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( @@ -340,7 +366,9 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( SMB Version @@ -363,12 +391,13 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Domain (Optional) - + Domain or workgroup for authentication (optional). @@ -376,7 +405,9 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for )} /> ( Port @@ -384,7 +415,8 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for field.onChange(parseInt(e.target.value, 10) || undefined)} /> diff --git a/apps/client/app/modules/details/tabs/docker.tsx b/apps/client/app/modules/details/tabs/docker.tsx index 2cb48f6..781165b 100644 --- a/apps/client/app/modules/details/tabs/docker.tsx +++ b/apps/client/app/modules/details/tabs/docker.tsx @@ -28,8 +28,15 @@ export const DockerTabContent = ({ volume }: Props) => { const dockerRunCommand = `docker run -v im-${volume.name}:/path/in/container nginx:latest`; - const containersQuery = getContainersUsingVolumeOptions({ path: { name: volume.name } }); - const { data: containersData, isLoading, error } = useQuery(containersQuery); + const { + data: containersData, + isLoading, + error, + } = useQuery({ + ...getContainersUsingVolumeOptions({ path: { name: volume.name } }), + refetchInterval: 10000, + refetchOnWindowFocus: true, + }); const containers = containersData?.containers || []; diff --git a/apps/server/package.json b/apps/server/package.json index 3f5cdfe..e5fd85d 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@hono/arktype-validator": "^2.0.1", + "@hono/standard-validator": "^0.1.5", "@ironmount/schemas": "workspace:*", "@scalar/hono-api-reference": "^0.9.13", "arktype": "^2.1.20", @@ -16,7 +17,7 @@ "dotenv": "^17.2.1", "drizzle-orm": "^0.44.4", "hono": "^4.9.2", - "hono-openapi": "^0.4.8", + "hono-openapi": "^1.1.0", "http-errors-enhanced": "^3.0.2", "node-cron": "^4.2.1", "slugify": "^1.6.6", diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index d13e05c..fa7badf 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -3,7 +3,7 @@ import { Scalar } from "@scalar/hono-api-reference"; import { Hono } from "hono"; import { serveStatic } from "hono/bun"; import { logger as honoLogger } from "hono/logger"; -import { openAPISpecs } from "hono-openapi"; +import { openAPIRouteHandler } from "hono-openapi"; import { runDbMigrations } from "./db/db"; import { driverController } from "./modules/driver/driver.controller"; import { startup } from "./modules/lifecycle/startup"; @@ -12,14 +12,14 @@ import { handleServiceError } from "./utils/errors"; import { logger } from "./utils/logger"; export const generalDescriptor = (app: Hono) => - openAPISpecs(app, { + openAPIRouteHandler(app, { documentation: { info: { title: "Ironmount API", version: "1.0.0", description: "API for managing volumes", }, - servers: [{ url: "http://localhost:3000", description: "Development Server" }], + servers: [{ url: "http://localhost:4096", description: "Development Server" }], }, }); diff --git a/apps/server/src/modules/backends/nfs/nfs-backend.ts b/apps/server/src/modules/backends/nfs/nfs-backend.ts index ebfd66e..3d3b209 100644 --- a/apps/server/src/modules/backends/nfs/nfs-backend.ts +++ b/apps/server/src/modules/backends/nfs/nfs-backend.ts @@ -1,12 +1,12 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import { BACKEND_STATUS, type BackendConfig } from "@ironmount/schemas"; -import type { VolumeBackend } from "../backend"; -import { logger } from "../../../utils/logger"; -import { withTimeout } from "../../../utils/timeout"; import { OPERATION_TIMEOUT } from "../../../core/constants"; import { toMessage } from "../../../utils/errors"; +import { logger } from "../../../utils/logger"; import { getMountForPath } from "../../../utils/mountinfo"; +import { withTimeout } from "../../../utils/timeout"; +import type { VolumeBackend } from "../backend"; import { createTestFile, executeMount, executeUnmount } from "../utils/backend-utils"; const mount = async (config: BackendConfig, path: string) => { @@ -78,10 +78,9 @@ const unmount = async (path: string) => { try { return await withTimeout(run(), OPERATION_TIMEOUT, "NFS unmount"); - } catch (err: any) { - const msg = err.stderr?.toString().trim() || err.message; - logger.error("Error unmounting NFS volume", { path, error: msg }); - return { status: BACKEND_STATUS.error, error: msg }; + } catch (err) { + logger.error("Error unmounting NFS volume", { path, error: toMessage(err) }); + return { status: BACKEND_STATUS.error, error: toMessage(err) }; } }; diff --git a/apps/server/src/modules/volumes/volume.controller.ts b/apps/server/src/modules/volumes/volume.controller.ts index 82044ab..ea123ef 100644 --- a/apps/server/src/modules/volumes/volume.controller.ts +++ b/apps/server/src/modules/volumes/volume.controller.ts @@ -1,5 +1,5 @@ import { Hono } from "hono"; -import { validator } from "hono-openapi/arktype"; +import { validator } from "hono-openapi"; import { createVolumeBody, createVolumeDto, diff --git a/apps/server/src/modules/volumes/volume.dto.ts b/apps/server/src/modules/volumes/volume.dto.ts index c139963..cdcd9ba 100644 --- a/apps/server/src/modules/volumes/volume.dto.ts +++ b/apps/server/src/modules/volumes/volume.dto.ts @@ -1,7 +1,6 @@ -import { volumeConfigSchema } from "@ironmount/schemas"; +import { volumeConfigSchemaNoUndefined } from "@ironmount/schemas"; import { type } from "arktype"; -import { describeRoute } from "hono-openapi"; -import { resolver } from "hono-openapi/arktype"; +import { describeRoute, resolver } from "hono-openapi"; const volumeSchema = type({ name: "string", @@ -12,7 +11,7 @@ const volumeSchema = type({ createdAt: "number", updatedAt: "number", lastHealthCheck: "number", - config: volumeConfigSchema, + config: volumeConfigSchemaNoUndefined, autoRemount: "boolean", }); @@ -30,7 +29,6 @@ export const listVolumesDto = describeRoute({ description: "List all volumes", tags: ["Volumes"], operationId: "listVolumes", - validateResponse: true, responses: { 200: { description: "A list of volumes", @@ -48,7 +46,7 @@ export const listVolumesDto = describeRoute({ */ export const createVolumeBody = type({ name: "string", - config: volumeConfigSchema, + config: volumeConfigSchemaNoUndefined, }); export const createVolumeResponse = type({ @@ -62,7 +60,6 @@ export const createVolumeResponse = type({ export const createVolumeDto = describeRoute({ description: "Create a new volume", operationId: "createVolume", - validateResponse: true, tags: ["Volumes"], responses: { 201: { @@ -86,7 +83,6 @@ export const deleteVolumeResponse = type({ export const deleteVolumeDto = describeRoute({ description: "Delete a volume", operationId: "deleteVolume", - validateResponse: true, tags: ["Volumes"], responses: { 200: { @@ -118,7 +114,6 @@ export type GetVolumeResponseDto = typeof getVolumeResponse.infer; export const getVolumeDto = describeRoute({ description: "Get a volume by name", operationId: "getVolume", - validateResponse: true, tags: ["Volumes"], responses: { 200: { @@ -140,7 +135,7 @@ export const getVolumeDto = describeRoute({ */ export const updateVolumeBody = type({ autoRemount: "boolean?", - config: volumeConfigSchema.optional(), + config: volumeConfigSchemaNoUndefined.optional(), }); export type UpdateVolumeBody = typeof updateVolumeBody.infer; @@ -153,7 +148,6 @@ export const updateVolumeResponse = type({ export const updateVolumeDto = describeRoute({ description: "Update a volume's configuration", operationId: "updateVolume", - validateResponse: true, tags: ["Volumes"], responses: { 200: { @@ -176,7 +170,7 @@ export type UpdateVolumeResponseDto = typeof updateVolumeResponse.infer; * Test connection */ export const testConnectionBody = type({ - config: volumeConfigSchema, + config: volumeConfigSchemaNoUndefined, }); export const testConnectionResponse = type({ @@ -187,7 +181,6 @@ export const testConnectionResponse = type({ export const testConnectionDto = describeRoute({ description: "Test connection to backend", operationId: "testConnection", - validateResponse: true, tags: ["Volumes"], responses: { 200: { @@ -212,7 +205,6 @@ export const mountVolumeResponse = type({ export const mountVolumeDto = describeRoute({ description: "Mount a volume", operationId: "mountVolume", - validateResponse: true, tags: ["Volumes"], responses: { 200: { @@ -240,7 +232,6 @@ export const unmountVolumeResponse = type({ export const unmountVolumeDto = describeRoute({ description: "Unmount a volume", operationId: "unmountVolume", - validateResponse: true, tags: ["Volumes"], responses: { 200: { @@ -265,7 +256,6 @@ export const healthCheckResponse = type({ export const healthCheckDto = describeRoute({ description: "Perform a health check on a volume", operationId: "healthCheckVolume", - validateResponse: true, tags: ["Volumes"], responses: { 200: { @@ -300,7 +290,6 @@ export type ListContainersResponseDto = typeof listContainersResponse.infer; export const getContainersDto = describeRoute({ description: "Get containers using a volume by name", operationId: "getContainersUsingVolume", - validateResponse: true, tags: ["Volumes"], responses: { 200: { diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index 5c99579..655b76e 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -13,9 +13,6 @@ "noImplicitOverride": true, "module": "preserve", "noEmit": true, - "lib": ["es2022"], - "paths": { - "~/*": ["./src/*"] - }, + "lib": ["es2022"] } } diff --git a/bun.lock b/bun.lock index 733fb01..1850905 100644 --- a/bun.lock +++ b/bun.lock @@ -62,6 +62,7 @@ "name": "@ironmount/server", "dependencies": { "@hono/arktype-validator": "^2.0.1", + "@hono/standard-validator": "^0.1.5", "@ironmount/schemas": "workspace:*", "@scalar/hono-api-reference": "^0.9.13", "arktype": "^2.1.20", @@ -69,7 +70,7 @@ "dotenv": "^17.2.1", "drizzle-orm": "^0.44.4", "hono": "^4.9.2", - "hono-openapi": "^0.4.8", + "hono-openapi": "^1.1.0", "http-errors-enhanced": "^3.0.2", "node-cron": "^4.2.1", "slugify": "^1.6.6", @@ -92,8 +93,6 @@ "packages": { "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], - "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@11.9.3", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ=="], - "@ark/schema": ["@ark/schema@0.46.0", "", { "dependencies": { "@ark/util": "0.46.0" } }, "sha512-c2UQdKgP2eqqDArfBqQIJppxJHvNNXuQPeuSPlDML4rjw+f1cu0qAlzOG4b8ujgm9ctIDWwhpyw6gjG5ledIVQ=="], "@ark/util": ["@ark/util@0.46.0", "", {}, "sha512-JPy/NGWn/lvf1WmGCPw2VGpBg5utZraE84I7wli18EDF3p3zc/e9WolT35tINeZO3l7C77SjqRJeAUoT0CvMRg=="], @@ -244,6 +243,8 @@ "@hono/arktype-validator": ["@hono/arktype-validator@2.0.1", "", { "peerDependencies": { "arktype": "^2.0.0-dev.14", "hono": "*" } }, "sha512-Z4PQFtzgbGneBap+TTViRIBAoUWbwEwg8PaKNqALAP6z9N2ksJI81PfcsSQNUzwtrn8LipkMvBb8/D9Pei2GJw=="], + "@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="], + "@hookform/resolvers": ["@hookform/resolvers@5.2.1", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-u0+6X58gkjMcxur1wRWokA7XsiiBJ6aK17aPZxhkoYiK5J+HcTx0Vhu9ovXe6H+dVpO6cjrn2FkJTryXEMlryQ=="], "@ironmount/client": ["@ironmount/client@workspace:apps/client"], @@ -460,6 +461,12 @@ "@scalar/types": ["@scalar/types@0.2.11", "", { "dependencies": { "@scalar/openapi-types": "0.3.7", "nanoid": "5.1.5", "zod": "3.24.1" } }, "sha512-SUZzGmoisWsYv33LmmT/ajvSlcl9ZDj9d5RncJ+wB9ZQ2l018xlfpDIH9Kdfo+6KCKQOe3LYLXfH4Lzm891Mag=="], + "@standard-community/standard-json": ["@standard-community/standard-json@0.3.5", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA=="], + + "@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.8", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.17.14", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-80ap74p5oy/SU4al5HkPwO5+NbN2wH/FBr6kwaE5ROq7AvcDFaxzUfTazewroNaCotbvdGcvzXb9oEoOIyfC/Q=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], "@tailwindcss/node": ["@tailwindcss/node@4.1.12", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.12" } }, "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ=="], @@ -624,8 +631,6 @@ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="], @@ -828,7 +833,7 @@ "hono": ["hono@4.9.2", "", {}, "sha512-UG2jXGS/gkLH42l/1uROnwXpkjvvxkl3kpopL3LBo27NuaDPI6xHNfuUSilIHcrBkPfl4y0z6y2ByI455TjNRw=="], - "hono-openapi": ["hono-openapi@0.4.8", "", { "dependencies": { "json-schema-walker": "^2.0.0" }, "peerDependencies": { "@hono/arktype-validator": "^2.0.0", "@hono/effect-validator": "^1.2.0", "@hono/typebox-validator": "^0.2.0 || ^0.3.0", "@hono/valibot-validator": "^0.5.1", "@hono/zod-validator": "^0.4.1", "@sinclair/typebox": "^0.34.9", "@valibot/to-json-schema": "^1.0.0-beta.3", "arktype": "^2.0.0", "effect": "^3.11.3", "hono": "^4.6.13", "openapi-types": "^12.1.3", "valibot": "^1.0.0-beta.9", "zod": "^3.23.8", "zod-openapi": "^4.0.0" }, "optionalPeers": ["@hono/arktype-validator", "@hono/effect-validator", "@hono/typebox-validator", "@hono/valibot-validator", "@hono/zod-validator", "@sinclair/typebox", "@valibot/to-json-schema", "arktype", "effect", "hono", "valibot", "zod", "zod-openapi"] }, "sha512-LYr5xdtD49M7hEAduV1PftOMzuT8ZNvkyWfh1DThkLsIr4RkvDb12UxgIiFbwrJB6FLtFXLoOZL9x4IeDk2+VA=="], + "hono-openapi": ["hono-openapi@1.1.0", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.8", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-eA5hN8D2O30EkPPUxWFilcZcThAe81TShbH38Y183ZZp8WkgMh4BrPEDeZ/EFN2tyDi3cmTgKTa3+oStyJX0UA=="], "hosted-git-info": ["hosted-git-info@6.1.3", "", { "dependencies": { "lru-cache": "^7.5.1" } }, "sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw=="], @@ -878,8 +883,6 @@ "json-parse-even-better-errors": ["json-parse-even-better-errors@3.0.2", "", {}, "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ=="], - "json-schema-walker": ["json-schema-walker@2.0.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", "clone": "^2.1.2" } }, "sha512-nXN2cMky0Iw7Af28w061hmxaPDaML5/bQD9nwm1lOoIKEGjHcRGxqWe4MfrkYThYAPjSUhmsp4bJNoLAyVn9Xw=="], - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="], @@ -1044,6 +1047,8 @@ "qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="], @@ -1280,7 +1285,7 @@ "zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], - "zod": ["zod@4.0.17", "", {}, "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ=="], + "zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -1300,12 +1305,8 @@ "@npmcli/git/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], - "@scalar/openapi-types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], - "@scalar/types/nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], - "@scalar/types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.5", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" }, "bundled": true }, "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="], diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts index 4f95851..5331d2c 100644 --- a/openapi-ts.config.ts +++ b/openapi-ts.config.ts @@ -1,7 +1,7 @@ import { defaultPlugins, defineConfig } from "@hey-api/openapi-ts"; export default defineConfig({ - input: "http://192.168.2.42:3000/api/v1/openapi.json", + input: "http://192.168.2.42:4096/api/v1/openapi.json", output: { path: "./apps/client/app/api-client", format: "biome", diff --git a/packages/schemas/src/index.ts b/packages/schemas/src/index.ts index cbf64b3..44c8c87 100644 --- a/packages/schemas/src/index.ts +++ b/packages/schemas/src/index.ts @@ -13,7 +13,7 @@ export const nfsConfigSchema = type({ backend: "'nfs'", server: "string", exportPath: "string", - port: type("string.integer.parse").or(type("number")).to("1 <= number <= 65536").default("2049"), + port: type("string.integer").or(type("number")).to("1 <= number <= 65536").default(2049), version: "'3' | '4' | '4.1'", }); @@ -24,8 +24,8 @@ export const smbConfigSchema = type({ username: "string", password: "string", vers: type("'1.0' | '2.0' | '2.1' | '3.0'").default("3.0"), - domain: "string?", - port: type("string.integer.parse").or(type("number")).to("1 <= number <= 65535").default(445), + domain: "string | undefined?", + port: type("string.integer").or(type("number")).to("1 <= number <= 65535").default(445), }); export const directoryConfigSchema = type({ @@ -36,13 +36,18 @@ export const webdavConfigSchema = type({ backend: "'webdav'", server: "string", path: "string", - username: "string?", - password: "string?", - port: type("string.integer.parse").or(type("number")).to("1 <= number <= 65536").default("80"), + username: "string | undefined?", + password: "string | undefined?", + port: type("string.integer").or(type("number")).to("1 <= number <= 65536").default(80), ssl: "boolean?", }); -export const volumeConfigSchema = nfsConfigSchema.or(smbConfigSchema).or(directoryConfigSchema).or(webdavConfigSchema); +export const volumeConfigSchemaNoUndefined = nfsConfigSchema + .or(smbConfigSchema.omit("domain").and(type({ domain: "string?" }))) + .or(webdavConfigSchema.omit("username", "password").and(type({ username: "string?", password: "string?" }))) + .or(directoryConfigSchema); + +export const volumeConfigSchema = nfsConfigSchema.or(smbConfigSchema).or(webdavConfigSchema).or(directoryConfigSchema); export type BackendConfig = typeof volumeConfigSchema.infer;