mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat: delete volume
This commit is contained in:
@@ -1,15 +1,13 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import { type Options, listVolumes, createVolume } from "../sdk.gen";
|
import { type Options, listVolumes, createVolume, deleteVolume } from "../sdk.gen";
|
||||||
import {
|
import { queryOptions, type UseMutationOptions, type DefaultError } from "@tanstack/react-query";
|
||||||
queryOptions,
|
|
||||||
type UseMutationOptions,
|
|
||||||
type DefaultError,
|
|
||||||
} from "@tanstack/react-query";
|
|
||||||
import type {
|
import type {
|
||||||
ListVolumesData,
|
ListVolumesData,
|
||||||
CreateVolumeData,
|
CreateVolumeData,
|
||||||
CreateVolumeResponse,
|
CreateVolumeResponse,
|
||||||
|
DeleteVolumeData,
|
||||||
|
DeleteVolumeResponse,
|
||||||
} from "../types.gen";
|
} from "../types.gen";
|
||||||
import { client as _heyApiClient } from "../client.gen";
|
import { client as _heyApiClient } from "../client.gen";
|
||||||
|
|
||||||
@@ -29,9 +27,7 @@ const createQueryKey = <TOptions extends Options>(
|
|||||||
): [QueryKey<TOptions>[0]] => {
|
): [QueryKey<TOptions>[0]] => {
|
||||||
const params: QueryKey<TOptions>[0] = {
|
const params: QueryKey<TOptions>[0] = {
|
||||||
_id: id,
|
_id: id,
|
||||||
baseUrl:
|
baseUrl: options?.baseUrl || (options?.client ?? _heyApiClient).getConfig().baseUrl,
|
||||||
options?.baseUrl ||
|
|
||||||
(options?.client ?? _heyApiClient).getConfig().baseUrl,
|
|
||||||
} as QueryKey<TOptions>[0];
|
} as QueryKey<TOptions>[0];
|
||||||
if (infinite) {
|
if (infinite) {
|
||||||
params._infinite = infinite;
|
params._infinite = infinite;
|
||||||
@@ -54,8 +50,7 @@ const createQueryKey = <TOptions extends Options>(
|
|||||||
return [params];
|
return [params];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listVolumesQueryKey = (options?: Options<ListVolumesData>) =>
|
export const listVolumesQueryKey = (options?: Options<ListVolumesData>) => createQueryKey("listVolumes", options);
|
||||||
createQueryKey("listVolumes", options);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all volumes
|
* List all volumes
|
||||||
@@ -75,8 +70,7 @@ export const listVolumesOptions = (options?: Options<ListVolumesData>) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createVolumeQueryKey = (options?: Options<CreateVolumeData>) =>
|
export const createVolumeQueryKey = (options?: Options<CreateVolumeData>) => createQueryKey("createVolume", options);
|
||||||
createQueryKey("createVolume", options);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new volume
|
* Create a new volume
|
||||||
@@ -101,16 +95,8 @@ export const createVolumeOptions = (options?: Options<CreateVolumeData>) => {
|
|||||||
*/
|
*/
|
||||||
export const createVolumeMutation = (
|
export const createVolumeMutation = (
|
||||||
options?: Partial<Options<CreateVolumeData>>,
|
options?: Partial<Options<CreateVolumeData>>,
|
||||||
): UseMutationOptions<
|
): UseMutationOptions<CreateVolumeResponse, DefaultError, Options<CreateVolumeData>> => {
|
||||||
CreateVolumeResponse,
|
const mutationOptions: UseMutationOptions<CreateVolumeResponse, DefaultError, Options<CreateVolumeData>> = {
|
||||||
DefaultError,
|
|
||||||
Options<CreateVolumeData>
|
|
||||||
> => {
|
|
||||||
const mutationOptions: UseMutationOptions<
|
|
||||||
CreateVolumeResponse,
|
|
||||||
DefaultError,
|
|
||||||
Options<CreateVolumeData>
|
|
||||||
> = {
|
|
||||||
mutationFn: async (localOptions) => {
|
mutationFn: async (localOptions) => {
|
||||||
const { data } = await createVolume({
|
const { data } = await createVolume({
|
||||||
...options,
|
...options,
|
||||||
@@ -122,3 +108,22 @@ export const createVolumeMutation = (
|
|||||||
};
|
};
|
||||||
return mutationOptions;
|
return mutationOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a volume
|
||||||
|
*/
|
||||||
|
export const deleteVolumeMutation = (
|
||||||
|
options?: Partial<Options<DeleteVolumeData>>,
|
||||||
|
): UseMutationOptions<DeleteVolumeResponse, DefaultError, Options<DeleteVolumeData>> => {
|
||||||
|
const mutationOptions: UseMutationOptions<DeleteVolumeResponse, DefaultError, Options<DeleteVolumeData>> = {
|
||||||
|
mutationFn: async (localOptions) => {
|
||||||
|
const { data } = await deleteVolume({
|
||||||
|
...options,
|
||||||
|
...localOptions,
|
||||||
|
throwOnError: true,
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return mutationOptions;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { ClientOptions } from "./types.gen";
|
import type { ClientOptions } from "./types.gen";
|
||||||
import {
|
import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from "./client";
|
||||||
type Config,
|
|
||||||
type ClientOptions as DefaultClientOptions,
|
|
||||||
createClient,
|
|
||||||
createConfig,
|
|
||||||
} from "./client";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `createClientConfig()` function will be called on client initialization
|
* The `createClientConfig()` function will be called on client initialization
|
||||||
@@ -16,10 +11,9 @@ import {
|
|||||||
* `setConfig()`. This is useful for example if you're using Next.js
|
* `setConfig()`. This is useful for example if you're using Next.js
|
||||||
* to ensure your client always has the correct values.
|
* to ensure your client always has the correct values.
|
||||||
*/
|
*/
|
||||||
export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> =
|
export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (
|
||||||
(
|
|
||||||
override?: Config<DefaultClientOptions & T>,
|
override?: Config<DefaultClientOptions & T>,
|
||||||
) => Config<Required<DefaultClientOptions> & T>;
|
) => Config<Required<DefaultClientOptions> & T>;
|
||||||
|
|
||||||
export const client = createClient(
|
export const client = createClient(
|
||||||
createConfig<ClientOptions>({
|
createConfig<ClientOptions>({
|
||||||
|
|||||||
@@ -26,12 +26,7 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
return getConfig();
|
return getConfig();
|
||||||
};
|
};
|
||||||
|
|
||||||
const interceptors = createInterceptors<
|
const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>();
|
||||||
Request,
|
|
||||||
Response,
|
|
||||||
unknown,
|
|
||||||
ResolvedRequestOptions
|
|
||||||
>();
|
|
||||||
|
|
||||||
const request: Client["request"] = async (options) => {
|
const request: Client["request"] = async (options) => {
|
||||||
const opts = {
|
const opts = {
|
||||||
@@ -94,10 +89,7 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (
|
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
||||||
response.status === 204 ||
|
|
||||||
response.headers.get("Content-Length") === "0"
|
|
||||||
) {
|
|
||||||
return opts.responseStyle === "data"
|
return opts.responseStyle === "data"
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
@@ -107,9 +99,7 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parseAs =
|
const parseAs =
|
||||||
(opts.parseAs === "auto"
|
(opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
||||||
? getParseAs(response.headers.get("Content-Type"))
|
|
||||||
: opts.parseAs) ?? "json";
|
|
||||||
|
|
||||||
let data: any;
|
let data: any;
|
||||||
switch (parseAs) {
|
switch (parseAs) {
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { Auth } from "../core/auth.gen";
|
import type { Auth } from "../core/auth.gen";
|
||||||
import type {
|
import type { Client as CoreClient, Config as CoreConfig } from "../core/types.gen";
|
||||||
Client as CoreClient,
|
|
||||||
Config as CoreConfig,
|
|
||||||
} from "../core/types.gen";
|
|
||||||
import type { Middleware } from "./utils.gen";
|
import type { Middleware } from "./utils.gen";
|
||||||
|
|
||||||
export type ResponseStyle = "data" | "fields";
|
export type ResponseStyle = "data" | "fields";
|
||||||
@@ -38,14 +35,7 @@ export interface Config<T extends ClientOptions = ClientOptions>
|
|||||||
*
|
*
|
||||||
* @default 'auto'
|
* @default 'auto'
|
||||||
*/
|
*/
|
||||||
parseAs?:
|
parseAs?: "arrayBuffer" | "auto" | "blob" | "formData" | "json" | "stream" | "text";
|
||||||
| "arrayBuffer"
|
|
||||||
| "auto"
|
|
||||||
| "blob"
|
|
||||||
| "formData"
|
|
||||||
| "json"
|
|
||||||
| "stream"
|
|
||||||
| "text";
|
|
||||||
/**
|
/**
|
||||||
* Should we return only data or multiple fields (data, error, response, etc.)?
|
* Should we return only data or multiple fields (data, error, response, etc.)?
|
||||||
*
|
*
|
||||||
@@ -103,32 +93,22 @@ export type RequestResult<
|
|||||||
? TData[keyof TData]
|
? TData[keyof TData]
|
||||||
: TData
|
: TData
|
||||||
: {
|
: {
|
||||||
data: TData extends Record<string, unknown>
|
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData;
|
||||||
? TData[keyof TData]
|
|
||||||
: TData;
|
|
||||||
request: Request;
|
request: Request;
|
||||||
response: Response;
|
response: Response;
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
: Promise<
|
: Promise<
|
||||||
TResponseStyle extends "data"
|
TResponseStyle extends "data"
|
||||||
?
|
? (TData extends Record<string, unknown> ? TData[keyof TData] : TData) | undefined
|
||||||
| (TData extends Record<string, unknown>
|
|
||||||
? TData[keyof TData]
|
|
||||||
: TData)
|
|
||||||
| undefined
|
|
||||||
: (
|
: (
|
||||||
| {
|
| {
|
||||||
data: TData extends Record<string, unknown>
|
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData;
|
||||||
? TData[keyof TData]
|
|
||||||
: TData;
|
|
||||||
error: undefined;
|
error: undefined;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
data: undefined;
|
data: undefined;
|
||||||
error: TError extends Record<string, unknown>
|
error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
|
||||||
? TError[keyof TError]
|
|
||||||
: TError;
|
|
||||||
}
|
}
|
||||||
) & {
|
) & {
|
||||||
request: Request;
|
request: Request;
|
||||||
@@ -202,11 +182,7 @@ export type Options<
|
|||||||
TData extends TDataShape = TDataShape,
|
TData extends TDataShape = TDataShape,
|
||||||
ThrowOnError extends boolean = boolean,
|
ThrowOnError extends boolean = boolean,
|
||||||
TResponseStyle extends ResponseStyle = "fields",
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
> = OmitKeys<
|
> = OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "path" | "query" | "url"> & Omit<TData, "url">;
|
||||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
|
||||||
"body" | "path" | "query" | "url"
|
|
||||||
> &
|
|
||||||
Omit<TData, "url">;
|
|
||||||
|
|
||||||
export type OptionsLegacyParser<
|
export type OptionsLegacyParser<
|
||||||
TData = unknown,
|
TData = unknown,
|
||||||
@@ -214,19 +190,12 @@ export type OptionsLegacyParser<
|
|||||||
TResponseStyle extends ResponseStyle = "fields",
|
TResponseStyle extends ResponseStyle = "fields",
|
||||||
> = TData extends { body?: any }
|
> = TData extends { body?: any }
|
||||||
? TData extends { headers?: any }
|
? TData extends { headers?: any }
|
||||||
? OmitKeys<
|
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "headers" | "url"> & TData
|
||||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
|
||||||
"body" | "headers" | "url"
|
|
||||||
> &
|
|
||||||
TData
|
|
||||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "url"> &
|
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "url"> &
|
||||||
TData &
|
TData &
|
||||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "headers">
|
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "headers">
|
||||||
: TData extends { headers?: any }
|
: TData extends { headers?: any }
|
||||||
? OmitKeys<
|
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "headers" | "url"> &
|
||||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
|
||||||
"headers" | "url"
|
|
||||||
> &
|
|
||||||
TData &
|
TData &
|
||||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "body">
|
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "body">
|
||||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "url"> & TData;
|
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "url"> & TData;
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import { getAuthToken } from "../core/auth.gen";
|
import { getAuthToken } from "../core/auth.gen";
|
||||||
import type {
|
import type { QuerySerializer, QuerySerializerOptions } from "../core/bodySerializer.gen";
|
||||||
QuerySerializer,
|
|
||||||
QuerySerializerOptions,
|
|
||||||
} from "../core/bodySerializer.gen";
|
|
||||||
import { jsonBodySerializer } from "../core/bodySerializer.gen";
|
import { jsonBodySerializer } from "../core/bodySerializer.gen";
|
||||||
import {
|
import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer.gen";
|
||||||
serializeArrayParam,
|
import type { Client, ClientOptions, Config, RequestOptions } from "./types.gen";
|
||||||
serializeObjectParam,
|
|
||||||
serializePrimitiveParam,
|
|
||||||
} from "../core/pathSerializer.gen";
|
|
||||||
import type {
|
|
||||||
Client,
|
|
||||||
ClientOptions,
|
|
||||||
Config,
|
|
||||||
RequestOptions,
|
|
||||||
} from "./types.gen";
|
|
||||||
|
|
||||||
interface PathSerializer {
|
interface PathSerializer {
|
||||||
path: Record<string, unknown>;
|
path: Record<string, unknown>;
|
||||||
@@ -58,10 +46,7 @@ const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
url = url.replace(
|
url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
|
||||||
match,
|
|
||||||
serializeArrayParam({ explode, name, style, value }),
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,20 +75,14 @@ const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaceValue = encodeURIComponent(
|
const replaceValue = encodeURIComponent(style === "label" ? `.${value as string}` : (value as string));
|
||||||
style === "label" ? `.${value as string}` : (value as string),
|
|
||||||
);
|
|
||||||
url = url.replace(match, replaceValue);
|
url = url.replace(match, replaceValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createQuerySerializer = <T = unknown>({
|
export const createQuerySerializer = <T = unknown>({ allowReserved, array, object }: QuerySerializerOptions = {}) => {
|
||||||
allowReserved,
|
|
||||||
array,
|
|
||||||
object,
|
|
||||||
}: QuerySerializerOptions = {}) => {
|
|
||||||
const querySerializer = (queryParams: T) => {
|
const querySerializer = (queryParams: T) => {
|
||||||
const search: string[] = [];
|
const search: string[] = [];
|
||||||
if (queryParams && typeof queryParams === "object") {
|
if (queryParams && typeof queryParams === "object") {
|
||||||
@@ -152,9 +131,7 @@ export const createQuerySerializer = <T = unknown>({
|
|||||||
/**
|
/**
|
||||||
* Infers parseAs value from provided Content-Type header.
|
* Infers parseAs value from provided Content-Type header.
|
||||||
*/
|
*/
|
||||||
export const getParseAs = (
|
export const getParseAs = (contentType: string | null): Exclude<Config["parseAs"], "auto"> => {
|
||||||
contentType: string | null,
|
|
||||||
): Exclude<Config["parseAs"], "auto"> => {
|
|
||||||
if (!contentType) {
|
if (!contentType) {
|
||||||
// If no Content-Type header is provided, the best we can do is return the raw response body,
|
// If no Content-Type header is provided, the best we can do is return the raw response body,
|
||||||
// which is effectively the same as the 'stream' option.
|
// which is effectively the same as the 'stream' option.
|
||||||
@@ -167,10 +144,7 @@ export const getParseAs = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
|
||||||
cleanContent.startsWith("application/json") ||
|
|
||||||
cleanContent.endsWith("+json")
|
|
||||||
) {
|
|
||||||
return "json";
|
return "json";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,11 +152,7 @@ export const getParseAs = (
|
|||||||
return "formData";
|
return "formData";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (["application/", "audio/", "image/", "video/"].some((type) => cleanContent.startsWith(type))) {
|
||||||
["application/", "audio/", "image/", "video/"].some((type) =>
|
|
||||||
cleanContent.startsWith(type),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return "blob";
|
return "blob";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,11 +172,7 @@ const checkForExistence = (
|
|||||||
if (!name) {
|
if (!name) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) {
|
||||||
options.headers.has(name) ||
|
|
||||||
options.query?.[name] ||
|
|
||||||
options.headers.get("Cookie")?.includes(`${name}=`)
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -301,17 +267,14 @@ export const mergeConfigs = (a: Config, b: Config): Config => {
|
|||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mergeHeaders = (
|
export const mergeHeaders = (...headers: Array<Required<Config>["headers"] | undefined>): Headers => {
|
||||||
...headers: Array<Required<Config>["headers"] | undefined>
|
|
||||||
): Headers => {
|
|
||||||
const mergedHeaders = new Headers();
|
const mergedHeaders = new Headers();
|
||||||
for (const header of headers) {
|
for (const header of headers) {
|
||||||
if (!header || typeof header !== "object") {
|
if (!header || typeof header !== "object") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iterator =
|
const iterator = header instanceof Headers ? header.entries() : Object.entries(header);
|
||||||
header instanceof Headers ? header.entries() : Object.entries(header);
|
|
||||||
|
|
||||||
for (const [key, value] of iterator) {
|
for (const [key, value] of iterator) {
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
@@ -323,10 +286,7 @@ export const mergeHeaders = (
|
|||||||
} else if (value !== undefined) {
|
} else if (value !== undefined) {
|
||||||
// assume object headers are meant to be JSON stringified, i.e. their
|
// assume object headers are meant to be JSON stringified, i.e. their
|
||||||
// content value in OpenAPI specification is 'application/json'
|
// content value in OpenAPI specification is 'application/json'
|
||||||
mergedHeaders.set(
|
mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : (value as string));
|
||||||
key,
|
|
||||||
typeof value === "object" ? JSON.stringify(value) : (value as string),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,16 +300,9 @@ type ErrInterceptor<Err, Res, Req, Options> = (
|
|||||||
options: Options,
|
options: Options,
|
||||||
) => Err | Promise<Err>;
|
) => Err | Promise<Err>;
|
||||||
|
|
||||||
type ReqInterceptor<Req, Options> = (
|
type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req>;
|
||||||
request: Req,
|
|
||||||
options: Options,
|
|
||||||
) => Req | Promise<Req>;
|
|
||||||
|
|
||||||
type ResInterceptor<Res, Req, Options> = (
|
type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>;
|
||||||
response: Res,
|
|
||||||
request: Req,
|
|
||||||
options: Options,
|
|
||||||
) => Res | Promise<Res>;
|
|
||||||
|
|
||||||
class Interceptors<Interceptor> {
|
class Interceptors<Interceptor> {
|
||||||
_fns: (Interceptor | null)[];
|
_fns: (Interceptor | null)[];
|
||||||
@@ -400,15 +353,9 @@ class Interceptors<Interceptor> {
|
|||||||
// `createInterceptors()` response, meant for external use as it does not
|
// `createInterceptors()` response, meant for external use as it does not
|
||||||
// expose internals
|
// expose internals
|
||||||
export interface Middleware<Req, Res, Err, Options> {
|
export interface Middleware<Req, Res, Err, Options> {
|
||||||
error: Pick<
|
error: Pick<Interceptors<ErrInterceptor<Err, Res, Req, Options>>, "eject" | "use">;
|
||||||
Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
|
|
||||||
"eject" | "use"
|
|
||||||
>;
|
|
||||||
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, "eject" | "use">;
|
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, "eject" | "use">;
|
||||||
response: Pick<
|
response: Pick<Interceptors<ResInterceptor<Res, Req, Options>>, "eject" | "use">;
|
||||||
Interceptors<ResInterceptor<Res, Req, Options>>,
|
|
||||||
"eject" | "use"
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not add `Middleware` as return type so we can use _fns internally
|
// do not add `Middleware` as return type so we can use _fns internally
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ export const getAuthToken = async (
|
|||||||
auth: Auth,
|
auth: Auth,
|
||||||
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
|
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
|
||||||
): Promise<string | undefined> => {
|
): Promise<string | undefined> => {
|
||||||
const token =
|
const token = typeof callback === "function" ? await callback(auth) : callback;
|
||||||
typeof callback === "function" ? await callback(auth) : callback;
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type {
|
import type { ArrayStyle, ObjectStyle, SerializerOptions } from "./pathSerializer.gen";
|
||||||
ArrayStyle,
|
|
||||||
ObjectStyle,
|
|
||||||
SerializerOptions,
|
|
||||||
} from "./pathSerializer.gen";
|
|
||||||
|
|
||||||
export type QuerySerializer = (query: Record<string, unknown>) => string;
|
export type QuerySerializer = (query: Record<string, unknown>) => string;
|
||||||
|
|
||||||
@@ -16,11 +12,7 @@ export interface QuerySerializerOptions {
|
|||||||
object?: SerializerOptions<ObjectStyle>;
|
object?: SerializerOptions<ObjectStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const serializeFormDataPair = (
|
const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => {
|
||||||
data: FormData,
|
|
||||||
key: string,
|
|
||||||
value: unknown,
|
|
||||||
): void => {
|
|
||||||
if (typeof value === "string" || value instanceof Blob) {
|
if (typeof value === "string" || value instanceof Blob) {
|
||||||
data.append(key, value);
|
data.append(key, value);
|
||||||
} else if (value instanceof Date) {
|
} else if (value instanceof Date) {
|
||||||
@@ -30,11 +22,7 @@ const serializeFormDataPair = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const serializeUrlSearchParamsPair = (
|
const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => {
|
||||||
data: URLSearchParams,
|
|
||||||
key: string,
|
|
||||||
value: unknown,
|
|
||||||
): void => {
|
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
data.append(key, value);
|
data.append(key, value);
|
||||||
} else {
|
} else {
|
||||||
@@ -43,9 +31,7 @@ const serializeUrlSearchParamsPair = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const formDataBodySerializer = {
|
export const formDataBodySerializer = {
|
||||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
|
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): FormData => {
|
||||||
body: T,
|
|
||||||
): FormData => {
|
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
|
|
||||||
Object.entries(body).forEach(([key, value]) => {
|
Object.entries(body).forEach(([key, value]) => {
|
||||||
@@ -65,15 +51,11 @@ export const formDataBodySerializer = {
|
|||||||
|
|
||||||
export const jsonBodySerializer = {
|
export const jsonBodySerializer = {
|
||||||
bodySerializer: <T>(body: T): string =>
|
bodySerializer: <T>(body: T): string =>
|
||||||
JSON.stringify(body, (_key, value) =>
|
JSON.stringify(body, (_key, value) => (typeof value === "bigint" ? value.toString() : value)),
|
||||||
typeof value === "bigint" ? value.toString() : value,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const urlSearchParamsBodySerializer = {
|
export const urlSearchParamsBodySerializer = {
|
||||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
|
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): string => {
|
||||||
body: T,
|
|
||||||
): string => {
|
|
||||||
const data = new URLSearchParams();
|
const data = new URLSearchParams();
|
||||||
|
|
||||||
Object.entries(body).forEach(([key, value]) => {
|
Object.entries(body).forEach(([key, value]) => {
|
||||||
|
|||||||
@@ -83,10 +83,7 @@ const stripEmptySlots = (params: Params) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildClientParams = (
|
export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsConfig) => {
|
||||||
args: ReadonlyArray<unknown>,
|
|
||||||
fields: FieldsConfig,
|
|
||||||
) => {
|
|
||||||
const params: Params = {
|
const params: Params = {
|
||||||
body: {},
|
body: {},
|
||||||
headers: {},
|
headers: {},
|
||||||
@@ -123,19 +120,13 @@ export const buildClientParams = (
|
|||||||
const name = field.map || key;
|
const name = field.map || key;
|
||||||
(params[field.in] as Record<string, unknown>)[name] = value;
|
(params[field.in] as Record<string, unknown>)[name] = value;
|
||||||
} else {
|
} else {
|
||||||
const extra = extraPrefixes.find(([prefix]) =>
|
const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix));
|
||||||
key.startsWith(prefix),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (extra) {
|
if (extra) {
|
||||||
const [prefix, slot] = extra;
|
const [prefix, slot] = extra;
|
||||||
(params[slot] as Record<string, unknown>)[
|
(params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value;
|
||||||
key.slice(prefix.length)
|
|
||||||
] = value;
|
|
||||||
} else {
|
} else {
|
||||||
for (const [slot, allowed] of Object.entries(
|
for (const [slot, allowed] of Object.entries(config.allowExtra ?? {})) {
|
||||||
config.allowExtra ?? {},
|
|
||||||
)) {
|
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
(params[slot as Slot] as Record<string, unknown>)[key] = value;
|
(params[slot as Slot] as Record<string, unknown>)[key] = value;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
interface SerializeOptions<T>
|
interface SerializeOptions<T> extends SerializePrimitiveOptions, SerializerOptions<T> {}
|
||||||
extends SerializePrimitiveOptions,
|
|
||||||
SerializerOptions<T> {}
|
|
||||||
|
|
||||||
interface SerializePrimitiveOptions {
|
interface SerializePrimitiveOptions {
|
||||||
allowReserved?: boolean;
|
allowReserved?: boolean;
|
||||||
@@ -76,9 +74,9 @@ export const serializeArrayParam = ({
|
|||||||
value: unknown[];
|
value: unknown[];
|
||||||
}) => {
|
}) => {
|
||||||
if (!explode) {
|
if (!explode) {
|
||||||
const joinedValues = (
|
const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v as string))).join(
|
||||||
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
|
separatorArrayNoExplode(style),
|
||||||
).join(separatorArrayNoExplode(style));
|
);
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case "label":
|
case "label":
|
||||||
return `.${joinedValues}`;
|
return `.${joinedValues}`;
|
||||||
@@ -105,16 +103,10 @@ export const serializeArrayParam = ({
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.join(separator);
|
.join(separator);
|
||||||
return style === "label" || style === "matrix"
|
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
||||||
? separator + joinedValues
|
|
||||||
: joinedValues;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const serializePrimitiveParam = ({
|
export const serializePrimitiveParam = ({ allowReserved, name, value }: SerializePrimitiveParam) => {
|
||||||
allowReserved,
|
|
||||||
name,
|
|
||||||
value,
|
|
||||||
}: SerializePrimitiveParam) => {
|
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -146,11 +138,7 @@ export const serializeObjectParam = ({
|
|||||||
if (style !== "deepObject" && !explode) {
|
if (style !== "deepObject" && !explode) {
|
||||||
let values: string[] = [];
|
let values: string[] = [];
|
||||||
Object.entries(value).forEach(([key, v]) => {
|
Object.entries(value).forEach(([key, v]) => {
|
||||||
values = [
|
values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)];
|
||||||
...values,
|
|
||||||
key,
|
|
||||||
allowReserved ? (v as string) : encodeURIComponent(v as string),
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
const joinedValues = values.join(",");
|
const joinedValues = values.join(",");
|
||||||
switch (style) {
|
switch (style) {
|
||||||
@@ -175,7 +163,5 @@ export const serializeObjectParam = ({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.join(separator);
|
.join(separator);
|
||||||
return style === "label" || style === "matrix"
|
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
||||||
? separator + joinedValues
|
|
||||||
: joinedValues;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
// This file is auto-generated by @hey-api/openapi-ts
|
// This file is auto-generated by @hey-api/openapi-ts
|
||||||
|
|
||||||
import type { Auth, AuthToken } from "./auth.gen";
|
import type { Auth, AuthToken } from "./auth.gen";
|
||||||
import type {
|
import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer.gen";
|
||||||
BodySerializer,
|
|
||||||
QuerySerializer,
|
|
||||||
QuerySerializerOptions,
|
|
||||||
} from "./bodySerializer.gen";
|
|
||||||
|
|
||||||
export interface Client<
|
export interface Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never> {
|
||||||
RequestFn = never,
|
|
||||||
Config = unknown,
|
|
||||||
MethodFn = never,
|
|
||||||
BuildUrlFn = never,
|
|
||||||
> {
|
|
||||||
/**
|
/**
|
||||||
* Returns the final request URL.
|
* Returns the final request URL.
|
||||||
*/
|
*/
|
||||||
@@ -50,31 +41,13 @@ export interface Config {
|
|||||||
*/
|
*/
|
||||||
headers?:
|
headers?:
|
||||||
| RequestInit["headers"]
|
| RequestInit["headers"]
|
||||||
| Record<
|
| Record<string, string | number | boolean | (string | number | boolean)[] | null | undefined | unknown>;
|
||||||
string,
|
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| boolean
|
|
||||||
| (string | number | boolean)[]
|
|
||||||
| null
|
|
||||||
| undefined
|
|
||||||
| unknown
|
|
||||||
>;
|
|
||||||
/**
|
/**
|
||||||
* The request method.
|
* The request method.
|
||||||
*
|
*
|
||||||
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
|
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
|
||||||
*/
|
*/
|
||||||
method?:
|
method?: "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE";
|
||||||
| "CONNECT"
|
|
||||||
| "DELETE"
|
|
||||||
| "GET"
|
|
||||||
| "HEAD"
|
|
||||||
| "OPTIONS"
|
|
||||||
| "PATCH"
|
|
||||||
| "POST"
|
|
||||||
| "PUT"
|
|
||||||
| "TRACE";
|
|
||||||
/**
|
/**
|
||||||
* A function for serializing request query parameters. By default, arrays
|
* A function for serializing request query parameters. By default, arrays
|
||||||
* will be exploded in form style, objects will be exploded in deepObject
|
* will be exploded in form style, objects will be exploded in deepObject
|
||||||
@@ -114,7 +87,5 @@ type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
|
|||||||
: false;
|
: false;
|
||||||
|
|
||||||
export type OmitNever<T extends Record<string, unknown>> = {
|
export type OmitNever<T extends Record<string, unknown>> = {
|
||||||
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
|
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true ? never : K]: T[K];
|
||||||
? never
|
|
||||||
: K]: T[K];
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ import type {
|
|||||||
ListVolumesResponses,
|
ListVolumesResponses,
|
||||||
CreateVolumeData,
|
CreateVolumeData,
|
||||||
CreateVolumeResponses,
|
CreateVolumeResponses,
|
||||||
|
DeleteVolumeData,
|
||||||
|
DeleteVolumeResponses,
|
||||||
} from "./types.gen";
|
} from "./types.gen";
|
||||||
import { client as _heyApiClient } from "./client.gen";
|
import { client as _heyApiClient } from "./client.gen";
|
||||||
|
|
||||||
export type Options<
|
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<
|
||||||
TData extends TDataShape = TDataShape,
|
TData,
|
||||||
ThrowOnError extends boolean = boolean,
|
ThrowOnError
|
||||||
> = ClientOptions<TData, ThrowOnError> & {
|
> & {
|
||||||
/**
|
/**
|
||||||
* You can provide a client instance returned by `createClient()` instead of
|
* You can provide a client instance returned by `createClient()` instead of
|
||||||
* individual options. This might be also useful if you want to implement a
|
* individual options. This might be also useful if you want to implement a
|
||||||
@@ -29,14 +31,8 @@ export type Options<
|
|||||||
/**
|
/**
|
||||||
* List all volumes
|
* List all volumes
|
||||||
*/
|
*/
|
||||||
export const listVolumes = <ThrowOnError extends boolean = false>(
|
export const listVolumes = <ThrowOnError extends boolean = false>(options?: Options<ListVolumesData, ThrowOnError>) => {
|
||||||
options?: Options<ListVolumesData, ThrowOnError>,
|
return (options?.client ?? _heyApiClient).get<ListVolumesResponses, unknown, ThrowOnError>({
|
||||||
) => {
|
|
||||||
return (options?.client ?? _heyApiClient).get<
|
|
||||||
ListVolumesResponses,
|
|
||||||
unknown,
|
|
||||||
ThrowOnError
|
|
||||||
>({
|
|
||||||
url: "/api/v1/volumes",
|
url: "/api/v1/volumes",
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
@@ -48,11 +44,7 @@ export const listVolumes = <ThrowOnError extends boolean = false>(
|
|||||||
export const createVolume = <ThrowOnError extends boolean = false>(
|
export const createVolume = <ThrowOnError extends boolean = false>(
|
||||||
options?: Options<CreateVolumeData, ThrowOnError>,
|
options?: Options<CreateVolumeData, ThrowOnError>,
|
||||||
) => {
|
) => {
|
||||||
return (options?.client ?? _heyApiClient).post<
|
return (options?.client ?? _heyApiClient).post<CreateVolumeResponses, unknown, ThrowOnError>({
|
||||||
CreateVolumeResponses,
|
|
||||||
unknown,
|
|
||||||
ThrowOnError
|
|
||||||
>({
|
|
||||||
url: "/api/v1/volumes",
|
url: "/api/v1/volumes",
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -61,3 +53,15 @@ export const createVolume = <ThrowOnError extends boolean = false>(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a volume
|
||||||
|
*/
|
||||||
|
export const deleteVolume = <ThrowOnError extends boolean = false>(
|
||||||
|
options: Options<DeleteVolumeData, ThrowOnError>,
|
||||||
|
) => {
|
||||||
|
return (options.client ?? _heyApiClient).delete<DeleteVolumeResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/api/v1/volumes/{name}",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,14 +14,13 @@ export type ListVolumesResponses = {
|
|||||||
200: {
|
200: {
|
||||||
volumes: Array<{
|
volumes: Array<{
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
mountpoint: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
path: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ListVolumesResponse =
|
export type ListVolumesResponse = ListVolumesResponses[keyof ListVolumesResponses];
|
||||||
ListVolumesResponses[keyof ListVolumesResponses];
|
|
||||||
|
|
||||||
export type CreateVolumeData = {
|
export type CreateVolumeData = {
|
||||||
body?: {
|
body?: {
|
||||||
@@ -51,14 +50,36 @@ export type CreateVolumeResponses = {
|
|||||||
* Volume created successfully
|
* Volume created successfully
|
||||||
*/
|
*/
|
||||||
201: {
|
201: {
|
||||||
|
message: string;
|
||||||
|
volume: {
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
mountpoint: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateVolumeResponse =
|
export type CreateVolumeResponse = CreateVolumeResponses[keyof CreateVolumeResponses];
|
||||||
CreateVolumeResponses[keyof CreateVolumeResponses];
|
|
||||||
|
export type DeleteVolumeData = {
|
||||||
|
body?: never;
|
||||||
|
path: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
query?: never;
|
||||||
|
url: "/api/v1/volumes/{name}";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteVolumeResponses = {
|
||||||
|
/**
|
||||||
|
* Volume deleted successfully
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteVolumeResponse = DeleteVolumeResponses[keyof DeleteVolumeResponses];
|
||||||
|
|
||||||
export type ClientOptions = {
|
export type ClientOptions = {
|
||||||
baseUrl: "http://localhost:3000" | (string & {});
|
baseUrl: "http://localhost:3000" | (string & {});
|
||||||
|
|||||||
23
apps/client/app/components/ui/sonner.tsx
Normal file
23
apps/client/app/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||||
|
|
||||||
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
|
const { theme = "system" } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
theme={theme as ToasterProps["theme"]}
|
||||||
|
className="toaster group"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--normal-bg": "var(--popover)",
|
||||||
|
"--normal-text": "var(--popover-foreground)",
|
||||||
|
"--normal-border": "var(--border)",
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Toaster };
|
||||||
7
apps/client/app/lib/errors.ts
Normal file
7
apps/client/app/lib/errors.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const parseError = (error?: unknown) => {
|
||||||
|
if (error && typeof error === "object" && "message" in error) {
|
||||||
|
return { message: error.message as string };
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import {
|
import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
|
||||||
isRouteErrorResponse,
|
import { Toaster } from "~/components/ui/sonner";
|
||||||
Links,
|
|
||||||
Meta,
|
|
||||||
Outlet,
|
|
||||||
Scripts,
|
|
||||||
ScrollRestoration,
|
|
||||||
} from "react-router";
|
|
||||||
|
|
||||||
import type { Route } from "./+types/root";
|
import type { Route } from "./+types/root";
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
@@ -43,6 +37,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<body className="h-screen">
|
<body className="h-screen">
|
||||||
{children}
|
{children}
|
||||||
|
<Toaster />
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
</body>
|
</body>
|
||||||
@@ -62,10 +57,7 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|||||||
|
|
||||||
if (isRouteErrorResponse(error)) {
|
if (isRouteErrorResponse(error)) {
|
||||||
message = error.status === 404 ? "404" : "Error";
|
message = error.status === 404 ? "404" : "Error";
|
||||||
details =
|
details = error.status === 404 ? "The requested page could not be found." : error.statusText || details;
|
||||||
error.status === 404
|
|
||||||
? "The requested page could not be found."
|
|
||||||
: error.statusText || details;
|
|
||||||
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
||||||
details = error.message;
|
details = error.message;
|
||||||
stack = error.stack;
|
stack = error.stack;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Copy, Folder } from "lucide-react";
|
import { Copy, Folder } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { listVolumes } from "~/api-client";
|
import { useFetcher } from "react-router";
|
||||||
|
import { createVolume, deleteVolume, listVolumes } from "~/api-client";
|
||||||
import { CreateVolumeDialog } from "~/components/create-volume-dialog";
|
import { CreateVolumeDialog } from "~/components/create-volume-dialog";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
@@ -8,7 +9,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~
|
|||||||
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import type { Route } from "./+types/home";
|
import type { Route } from "./+types/home";
|
||||||
import { useFetcher } from "react-router";
|
import { parseError } from "~/lib/errors";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
export function meta(_: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
@@ -25,17 +27,29 @@ export async function clientAction({ request }: Route.ClientActionArgs) {
|
|||||||
const { _action, ...rest } = Object.fromEntries(formData.entries());
|
const { _action, ...rest } = Object.fromEntries(formData.entries());
|
||||||
|
|
||||||
if (_action === "delete") {
|
if (_action === "delete") {
|
||||||
return { yolo: "swag", _action: "delete" as const };
|
const { error } = await deleteVolume({ path: { name: rest.name as string } });
|
||||||
console.log("Delete action triggered", rest);
|
|
||||||
// Delete volume logic
|
if (error) {
|
||||||
|
toast.error("Failed to delete volume", {
|
||||||
|
description: parseError(error)?.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast.success("Volume deleted successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { error: parseError(error), _action: "delete" as const };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_action === "create") {
|
if (_action === "create") {
|
||||||
console.log("Create action triggered", rest);
|
const { error } = await createVolume({ body: { name: rest.name as string, config: { backend: "directory" } } });
|
||||||
return {
|
if (error) {
|
||||||
error: "Volume with this name already exists.",
|
toast.error("Failed to create volume", {
|
||||||
_action: "create" as const,
|
description: parseError(error)?.message,
|
||||||
};
|
});
|
||||||
|
} else {
|
||||||
|
toast.success("Volume created successfully");
|
||||||
|
}
|
||||||
|
return { error: parseError(error), _action: "create" as const };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +145,7 @@ export default function Home({ loaderData, actionData }: Route.ComponentProps) {
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<span className="text-muted-foreground text-xs truncate bg-primary/10 rounded-md px-2 py-1">
|
<span className="text-muted-foreground text-xs truncate bg-primary/10 rounded-md px-2 py-1">
|
||||||
{volume.mountpoint}
|
{volume.path}
|
||||||
</span>
|
</span>
|
||||||
<Copy size={10} />
|
<Copy size={10} />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
"typecheck": "react-router typegen && tsc"
|
"typecheck": "react-router typegen && tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ironmount/server": "workspace:*",
|
|
||||||
"@hookform/resolvers": "^5.2.1",
|
"@hookform/resolvers": "^5.2.1",
|
||||||
|
"@ironmount/server": "workspace:*",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
@@ -25,10 +25,12 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"isbot": "^5.1.27",
|
"isbot": "^5.1.27",
|
||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-hook-form": "^7.62.0",
|
"react-hook-form": "^7.62.0",
|
||||||
"react-router": "^7.7.1",
|
"react-router": "^7.7.1",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"zod": "^4.0.17"
|
"zod": "^4.0.17"
|
||||||
},
|
},
|
||||||
@@ -45,4 +47,3 @@
|
|||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ const directoryConfigSchema = type({
|
|||||||
backend: "'directory'",
|
backend: "'directory'",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const volumeConfigSchema = nfsConfigSchema
|
export const volumeConfigSchema = nfsConfigSchema.or(smbConfigSchema).or(directoryConfigSchema);
|
||||||
.or(smbConfigSchema)
|
|
||||||
.or(directoryConfigSchema);
|
|
||||||
|
|
||||||
export type BackendConfig = typeof volumeConfigSchema.infer;
|
export type BackendConfig = typeof volumeConfigSchema.infer;
|
||||||
|
|
||||||
@@ -36,11 +34,9 @@ export const volumesTable = sqliteTable("volumes_table", {
|
|||||||
name: text().notNull().unique(),
|
name: text().notNull().unique(),
|
||||||
path: text().notNull(),
|
path: text().notNull(),
|
||||||
type: text().notNull(),
|
type: text().notNull(),
|
||||||
createdAt: int("created_at").notNull().default(sql`(current_timestamp)`),
|
createdAt: int("created_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`),
|
||||||
updatedAt: int("updated_at").notNull().default(sql`(current_timestamp)`),
|
updatedAt: int("updated_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`),
|
||||||
config: text("config", { mode: "json" })
|
config: text("config", { mode: "json" }).$type<typeof volumeConfigSchema.inferOut>().notNull(),
|
||||||
.$type<typeof volumeConfigSchema.inferOut>()
|
|
||||||
.notNull(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Volume = typeof volumesTable.$inferSelect;
|
export type Volume = typeof volumesTable.$inferSelect;
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { validator } from "hono-openapi/arktype";
|
import { validator } from "hono-openapi/arktype";
|
||||||
import { handleServiceError } from "../../utils/errors";
|
import { handleServiceError } from "../../utils/errors";
|
||||||
import { createVolumeBody, createVolumeDto, type ListVolumesResponseDto, listVolumesDto } from "./volume.dto";
|
import {
|
||||||
|
createVolumeBody,
|
||||||
|
createVolumeDto,
|
||||||
|
deleteVolumeDto,
|
||||||
|
type ListVolumesResponseDto,
|
||||||
|
listVolumesDto,
|
||||||
|
} from "./volume.dto";
|
||||||
import { volumeService } from "./volume.service";
|
import { volumeService } from "./volume.service";
|
||||||
|
|
||||||
export const volumeController = new Hono()
|
export const volumeController = new Hono()
|
||||||
@@ -11,8 +17,8 @@ export const volumeController = new Hono()
|
|||||||
const response = {
|
const response = {
|
||||||
volumes: volumes.map((volume) => ({
|
volumes: volumes.map((volume) => ({
|
||||||
name: volume.name,
|
name: volume.name,
|
||||||
mountpoint: volume.path,
|
path: volume.path,
|
||||||
createdAt: volume.createdAt,
|
createdAt: volume.createdAt.getTime(),
|
||||||
})),
|
})),
|
||||||
} satisfies ListVolumesResponseDto;
|
} satisfies ListVolumesResponseDto;
|
||||||
|
|
||||||
@@ -29,6 +35,17 @@ export const volumeController = new Hono()
|
|||||||
|
|
||||||
return c.json({ message: "Volume created", volume: res.volume });
|
return c.json({ message: "Volume created", volume: res.volume });
|
||||||
})
|
})
|
||||||
|
.delete("/:name", deleteVolumeDto, async (c) => {
|
||||||
|
const { name } = c.req.param();
|
||||||
|
const res = await volumeService.deleteVolume(name);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
const { message, status } = handleServiceError(res.error);
|
||||||
|
return c.json(message, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.json({ message: "Volume deleted" });
|
||||||
|
})
|
||||||
.get("/:name", (c) => {
|
.get("/:name", (c) => {
|
||||||
return c.json({ message: `Details of volume ${c.req.param("name")}` });
|
return c.json({ message: `Details of volume ${c.req.param("name")}` });
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { volumeConfigSchema } from "../../db/schema";
|
|||||||
export const listVolumesResponse = type({
|
export const listVolumesResponse = type({
|
||||||
volumes: type({
|
volumes: type({
|
||||||
name: "string",
|
name: "string",
|
||||||
mountpoint: "string",
|
path: "string",
|
||||||
createdAt: "number",
|
createdAt: "number",
|
||||||
}).array(),
|
}).array(),
|
||||||
});
|
});
|
||||||
@@ -41,9 +41,12 @@ export const createVolumeBody = type({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const createVolumeResponse = type({
|
export const createVolumeResponse = type({
|
||||||
|
message: "string",
|
||||||
|
volume: type({
|
||||||
name: "string",
|
name: "string",
|
||||||
mountpoint: "string",
|
path: "string",
|
||||||
createdAt: "number",
|
createdAt: "number",
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createVolumeDto = describeRoute({
|
export const createVolumeDto = describeRoute({
|
||||||
@@ -62,3 +65,27 @@ export const createVolumeDto = describeRoute({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a volume
|
||||||
|
*/
|
||||||
|
export const deleteVolumeResponse = type({
|
||||||
|
message: "string",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteVolumeDto = describeRoute({
|
||||||
|
description: "Delete a volume",
|
||||||
|
operationId: "deleteVolume",
|
||||||
|
validateResponse: true,
|
||||||
|
tags: ["Volumes"],
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Volume deleted successfully",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(deleteVolumeResponse),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -39,6 +39,29 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => {
|
|||||||
return { volume: val[0], status: 201 };
|
return { volume: val[0], status: 201 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteVolume = async (name: string) => {
|
||||||
|
try {
|
||||||
|
const volume = await db.query.volumesTable.findFirst({
|
||||||
|
where: eq(volumesTable.name, name),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!volume) {
|
||||||
|
return { error: new NotFoundError("Volume not found") };
|
||||||
|
}
|
||||||
|
|
||||||
|
const backend = createVolumeBackend(volume);
|
||||||
|
await backend.unmount();
|
||||||
|
await db.delete(volumesTable).where(eq(volumesTable.name, name));
|
||||||
|
return { status: 200 };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
error: new InternalServerError("Failed to delete volume", {
|
||||||
|
cause: error,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const mountVolume = async (name: string) => {
|
const mountVolume = async (name: string) => {
|
||||||
try {
|
try {
|
||||||
const volume = await db.query.volumesTable.findFirst({
|
const volume = await db.query.volumesTable.findFirst({
|
||||||
@@ -64,4 +87,5 @@ export const volumeService = {
|
|||||||
listVolumes,
|
listVolumes,
|
||||||
createVolume,
|
createVolume,
|
||||||
mountVolume,
|
mountVolume,
|
||||||
|
deleteVolume,
|
||||||
};
|
};
|
||||||
|
|||||||
6
bun.lock
6
bun.lock
@@ -27,10 +27,12 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"isbot": "^5.1.27",
|
"isbot": "^5.1.27",
|
||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-hook-form": "^7.62.0",
|
"react-hook-form": "^7.62.0",
|
||||||
"react-router": "^7.7.1",
|
"react-router": "^7.7.1",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"zod": "^4.0.17",
|
"zod": "^4.0.17",
|
||||||
},
|
},
|
||||||
@@ -723,6 +725,8 @@
|
|||||||
|
|
||||||
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
||||||
|
|
||||||
|
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||||
|
|
||||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||||
|
|
||||||
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
|
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
|
||||||
@@ -851,6 +855,8 @@
|
|||||||
|
|
||||||
"slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="],
|
"slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="],
|
||||||
|
|
||||||
|
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
||||||
|
|
||||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|||||||
Reference in New Issue
Block a user