feat: #1243 add api routes (#1286)

This commit is contained in:
Manuel
2024-11-23 22:05:44 +01:00
committed by GitHub
parent 982ab4393e
commit d76b4d0ec1
17 changed files with 447 additions and 2471 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import type { OpenAPIV3 } from "openapi-types";
import type { OpenAPIObject } from "openapi3-ts/oas31";
import SwaggerUI from "swagger-ui-react";
// workaround for CSS that cannot be processed by next.js, https://github.com/swagger-api/swagger-ui/issues/10045
@@ -9,7 +9,7 @@ import "../swagger-ui-overrides.css";
import "../swagger-ui.css";
interface SwaggerUIClientProps {
document: OpenAPIV3.Document;
document: OpenAPIObject;
}
export const SwaggerUIClient = ({ document }: SwaggerUIClientProps) => {

View File

@@ -31,7 +31,9 @@ export const DeleteUserButton = ({ user }: DeleteUserButtonProps) => {
children: t("user.action.delete.confirm", { username: user.name }),
// eslint-disable-next-line no-restricted-syntax
async onConfirm() {
await mutateUserDeletionAsync(user.id);
await mutateUserDeletionAsync({
userId: user.id,
});
},
}),
[user, mutateUserDeletionAsync, openConfirmModal, t],

View File

@@ -1,6 +1,7 @@
"use client";
import { Button, Group, Radio, Stack } from "@mantine/core";
import type { DayOfWeek } from "@mantine/dates";
import dayjs from "dayjs";
import localeData from "dayjs/plugin/localeData";
@@ -43,7 +44,7 @@ export const FirstDayOfWeek = ({ user }: FirstDayOfWeekProps) => {
});
const form = useZodForm(validation.user.firstDayOfWeek, {
initialValues: {
firstDayOfWeek: user.firstDayOfWeek,
firstDayOfWeek: user.firstDayOfWeek as DayOfWeek,
},
});

View File

@@ -1,7 +1,7 @@
import { headers } from "next/headers";
import { userAgent } from "next/server";
import type { NextRequest } from "next/server";
import { createOpenApiFetchHandler } from "trpc-swagger/build/index.mjs";
import { createOpenApiFetchHandler } from "trpc-to-openapi";
import { appRouter, createTRPCContext } from "@homarr/api";
import { hashPasswordAsync } from "@homarr/auth";

View File

@@ -45,10 +45,5 @@
"packageManager": "pnpm@9.14.2",
"engines": {
"node": ">=22.11.0"
},
"pnpm": {
"patchedDependencies": {
"trpc-swagger@1.2.6": "patches/trpc-swagger@1.2.6.patch"
}
}
}

View File

@@ -43,7 +43,7 @@
"next": "^14.2.18",
"react": "^18.3.1",
"superjson": "2.2.1",
"trpc-swagger": "^1.2.6"
"trpc-to-openapi": "^2.0.2"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",

View File

@@ -1,4 +1,4 @@
import { generateOpenApiDocument } from "trpc-swagger";
import { generateOpenApiDocument } from "trpc-to-openapi";
import { appRouter } from "./root";

View File

@@ -2,25 +2,17 @@ import { TRPCError } from "@trpc/server";
import { asc, createId, eq, inArray, like } from "@homarr/db";
import { apps } from "@homarr/db/schema/sqlite";
import { selectAppSchema } from "@homarr/db/validationSchemas";
import { validation, z } from "@homarr/validation";
import { convertIntersectionToZodObject } from "../schema-merger";
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
import { canUserSeeAppAsync } from "./app/app-access-control";
export const appRouter = createTRPCRouter({
all: protectedProcedure
.input(z.void())
.output(
z.array(
z.object({
name: z.string(),
id: z.string(),
description: z.string().nullable(),
iconUrl: z.string(),
href: z.string().nullable(),
}),
),
)
.output(z.array(selectAppSchema))
.meta({ openapi: { method: "GET", path: "/api/apps", tags: ["apps"], protect: true } })
.query(({ ctx }) => {
return ctx.db.query.apps.findMany({
@@ -29,17 +21,7 @@ export const appRouter = createTRPCRouter({
}),
search: protectedProcedure
.input(z.object({ query: z.string(), limit: z.number().min(1).max(100).default(10) }))
.output(
z.array(
z.object({
name: z.string(),
id: z.string(),
description: z.string().nullable(),
iconUrl: z.string(),
href: z.string().nullable(),
}),
),
)
.output(z.array(selectAppSchema))
.meta({ openapi: { method: "GET", path: "/api/apps/search", tags: ["apps"], protect: true } })
.query(({ ctx, input }) => {
return ctx.db.query.apps.findMany({
@@ -50,17 +32,7 @@ export const appRouter = createTRPCRouter({
}),
selectable: protectedProcedure
.input(z.void())
.output(
z.array(
z.object({
name: z.string(),
id: z.string(),
iconUrl: z.string(),
description: z.string().nullable(),
href: z.string().nullable(),
}),
),
)
.output(z.array(selectAppSchema.pick({ id: true, name: true, iconUrl: true, href: true, description: true })))
.meta({
openapi: {
method: "GET",
@@ -83,15 +55,7 @@ export const appRouter = createTRPCRouter({
}),
byId: publicProcedure
.input(validation.common.byId)
.output(
z.object({
name: z.string(),
id: z.string(),
description: z.string().nullable(),
iconUrl: z.string(),
href: z.string().nullable(),
}),
)
.output(selectAppSchema)
.meta({ openapi: { method: "GET", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
.query(async ({ ctx, input }) => {
const app = await ctx.db.query.apps.findFirst({
@@ -136,7 +100,9 @@ export const appRouter = createTRPCRouter({
}),
update: permissionRequiredProcedure
.requiresPermission("app-modify-all")
.input(validation.app.edit)
.input(convertIntersectionToZodObject(validation.app.edit))
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
.mutation(async ({ ctx, input }) => {
const app = await ctx.db.query.apps.findFirst({
where: eq(apps.id, input.id),

View File

@@ -3,36 +3,51 @@ import { TRPCError } from "@trpc/server";
import { asc, createId, eq } from "@homarr/db";
import { invites } from "@homarr/db/schema/sqlite";
import { selectInviteSchema } from "@homarr/db/validationSchemas";
import { z } from "@homarr/validation";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { throwIfCredentialsDisabled } from "./invite/checks";
export const inviteRouter = createTRPCRouter({
getAll: protectedProcedure.query(async ({ ctx }) => {
throwIfCredentialsDisabled();
const dbInvites = await ctx.db.query.invites.findMany({
orderBy: asc(invites.expirationDate),
columns: {
token: false,
},
with: {
creator: {
columns: {
getAll: protectedProcedure
.output(
z.array(
selectInviteSchema
.pick({
id: true,
name: true,
expirationDate: true,
})
.extend({ creator: z.object({ name: z.string().nullable(), id: z.string() }) }),
),
)
.input(z.undefined())
.meta({ openapi: { method: "GET", path: "/api/invites", tags: ["invites"], protect: true } })
.query(async ({ ctx }) => {
throwIfCredentialsDisabled();
return await ctx.db.query.invites.findMany({
orderBy: asc(invites.expirationDate),
columns: {
token: false,
},
with: {
creator: {
columns: {
id: true,
name: true,
},
},
},
},
});
return dbInvites;
}),
});
}),
createInvite: protectedProcedure
.input(
z.object({
expirationDate: z.date(),
}),
)
.output(z.object({ id: z.string(), token: z.string() }))
.meta({ openapi: { method: "POST", path: "/api/invites", tags: ["invites"], protect: true } })
.mutation(async ({ ctx, input }) => {
throwIfCredentialsDisabled();
const id = createId();
@@ -56,6 +71,8 @@ export const inviteRouter = createTRPCRouter({
id: z.string(),
}),
)
.output(z.undefined())
.meta({ openapi: { method: "DELETE", path: "/api/invites/{id}", tags: ["invites"], protect: true } })
.mutation(async ({ ctx, input }) => {
throwIfCredentialsDisabled();
const dbInvite = await ctx.db.query.invites.findFirst({

View File

@@ -316,7 +316,7 @@ describe("delete should delete user", () => {
await db.insert(schema.users).values(initialUsers);
await caller.delete(defaultOwnerId);
await caller.delete({ userId: defaultOwnerId });
const usersInDb = await db.select().from(schema.users);
expect(usersInDb).toHaveLength(2);

View File

@@ -4,10 +4,12 @@ import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
import type { Database } from "@homarr/db";
import { and, createId, eq, like, schema } from "@homarr/db";
import { groupMembers, groupPermissions, groups, invites, users } from "@homarr/db/schema/sqlite";
import { selectUserSchema } from "@homarr/db/validationSchemas";
import type { SupportedAuthProvider } from "@homarr/definitions";
import { logger } from "@homarr/log";
import { validation, z } from "@homarr/validation";
import { convertIntersectionToZodObject } from "../schema-merger";
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
import { throwIfCredentialsDisabled } from "./invite/checks";
@@ -44,31 +46,34 @@ export const userRouter = createTRPCRouter({
userId,
});
}),
register: publicProcedure.input(validation.user.registrationApi).mutation(async ({ ctx, input }) => {
throwIfCredentialsDisabled();
const inviteWhere = and(eq(invites.id, input.inviteId), eq(invites.token, input.token));
const dbInvite = await ctx.db.query.invites.findFirst({
columns: {
id: true,
expirationDate: true,
},
where: inviteWhere,
});
if (!dbInvite || dbInvite.expirationDate < new Date()) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Invalid invite",
register: publicProcedure
.input(validation.user.registrationApi)
.output(z.void())
.mutation(async ({ ctx, input }) => {
throwIfCredentialsDisabled();
const inviteWhere = and(eq(invites.id, input.inviteId), eq(invites.token, input.token));
const dbInvite = await ctx.db.query.invites.findFirst({
columns: {
id: true,
expirationDate: true,
},
where: inviteWhere,
});
}
await checkUsernameAlreadyTakenAndThrowAsync(ctx.db, "credentials", input.username);
if (!dbInvite || dbInvite.expirationDate < new Date()) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Invalid invite",
});
}
await createUserAsync(ctx.db, input);
await checkUsernameAlreadyTakenAndThrowAsync(ctx.db, "credentials", input.username);
// Delete invite as it's used
await ctx.db.delete(invites).where(inviteWhere);
}),
await createUserAsync(ctx.db, input);
// Delete invite as it's used
await ctx.db.delete(invites).where(inviteWhere);
}),
create: permissionRequiredProcedure
.requiresPermission("admin")
.meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } })
@@ -85,6 +90,8 @@ export const userRouter = createTRPCRouter({
}
}),
setProfileImage: protectedProcedure
.output(z.void())
.meta({ openapi: { method: "PUT", path: "/api/users/profileImage", tags: ["users"], protect: true } })
.input(
z.object({
userId: z.string(),
@@ -138,17 +145,7 @@ export const userRouter = createTRPCRouter({
getAll: permissionRequiredProcedure
.requiresPermission("admin")
.input(z.void())
.output(
z.array(
z.object({
id: z.string(),
name: z.string().nullable(),
email: z.string().nullable(),
emailVerified: z.date().nullable(),
image: z.string().nullable(),
}),
),
)
.output(z.array(selectUserSchema.pick({ id: true, name: true, email: true, emailVerified: true, image: true })))
.meta({ openapi: { method: "GET", path: "/api/users", tags: ["users"], protect: true } })
.query(({ ctx }) => {
return ctx.db.query.users.findMany({
@@ -162,15 +159,19 @@ export const userRouter = createTRPCRouter({
});
}),
// Is protected because also used in board access / integration access forms
selectable: protectedProcedure.query(({ ctx }) => {
return ctx.db.query.users.findMany({
columns: {
id: true,
name: true,
image: true,
},
});
}),
selectable: protectedProcedure
.input(z.undefined())
.output(z.array(selectUserSchema.pick({ id: true, name: true, image: true })))
.meta({ openapi: { method: "GET", path: "/api/users/selectable", tags: ["users"], protect: true } })
.query(({ ctx }) => {
return ctx.db.query.users.findMany({
columns: {
id: true,
name: true,
image: true,
},
});
}),
search: permissionRequiredProcedure
.requiresPermission("admin")
.input(
@@ -179,6 +180,8 @@ export const userRouter = createTRPCRouter({
limit: z.number().min(1).max(100).default(10),
}),
)
.output(z.array(selectUserSchema.pick({ id: true, name: true, image: true })))
.meta({ openapi: { method: "POST", path: "/api/users/search", tags: ["users"], protect: true } })
.query(async ({ input, ctx }) => {
const dbUsers = await ctx.db.query.users.findMany({
columns: {
@@ -195,16 +198,10 @@ export const userRouter = createTRPCRouter({
image: user.image,
}));
}),
getById: protectedProcedure.input(z.object({ userId: z.string() })).query(async ({ input, ctx }) => {
// Only admins can view other users details
if (ctx.session.user.id !== input.userId && !ctx.session.user.permissions.includes("admin")) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You are not allowed to view other users details",
});
}
const user = await ctx.db.query.users.findFirst({
columns: {
getById: protectedProcedure
.input(z.object({ userId: z.string() }))
.output(
selectUserSchema.pick({
id: true,
name: true,
email: true,
@@ -214,134 +211,170 @@ export const userRouter = createTRPCRouter({
homeBoardId: true,
firstDayOfWeek: true,
pingIconsEnabled: true,
},
where: eq(users.id, input.userId),
});
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
return user;
}),
editProfile: protectedProcedure.input(validation.user.editProfile).mutation(async ({ input, ctx }) => {
// Only admins can view other users details
if (ctx.session.user.id !== input.id && !ctx.session.user.permissions.includes("admin")) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You are not allowed to edit other users details",
});
}
const user = await ctx.db.query.users.findFirst({
columns: { email: true, provider: true },
where: eq(users.id, input.id),
});
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
if (user.provider !== "credentials") {
throw new TRPCError({
code: "FORBIDDEN",
message: "Username and email can not be changed for users with external providers",
});
}
await checkUsernameAlreadyTakenAndThrowAsync(ctx.db, "credentials", input.name, input.id);
const emailDirty = input.email && user.email !== input.email;
await ctx.db
.update(users)
.set({
name: input.name,
email: emailDirty === true ? input.email : undefined,
emailVerified: emailDirty === true ? null : undefined,
})
.where(eq(users.id, input.id));
}),
delete: protectedProcedure.input(z.string()).mutation(async ({ input, ctx }) => {
// Only admins and user itself can delete a user
if (ctx.session.user.id !== input && !ctx.session.user.permissions.includes("admin")) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You are not allowed to delete other users",
});
}
await ctx.db.delete(users).where(eq(users.id, input));
}),
changePassword: protectedProcedure.input(validation.user.changePasswordApi).mutation(async ({ ctx, input }) => {
const user = ctx.session.user;
// Only admins can change other users' passwords
if (!user.permissions.includes("admin") && user.id !== input.userId) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
const dbUser = await ctx.db.query.users.findFirst({
columns: {
id: true,
password: true,
salt: true,
provider: true,
},
where: eq(users.id, input.userId),
});
if (!dbUser) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
if (dbUser.provider !== "credentials") {
throw new TRPCError({
code: "FORBIDDEN",
message: "Password can not be changed for users with external providers",
});
}
// Admins can change the password of other users without providing the previous password
const isPreviousPasswordRequired = ctx.session.user.id === input.userId;
logger.info(
`User ${user.id} is changing password for user ${input.userId}, previous password is required: ${isPreviousPasswordRequired}`,
);
if (isPreviousPasswordRequired) {
const previousPasswordHash = await hashPasswordAsync(input.previousPassword, dbUser.salt ?? "");
const isValid = previousPasswordHash === dbUser.password;
if (!isValid) {
}),
)
.meta({ openapi: { method: "GET", path: "/api/users/{userId}", tags: ["users"], protect: true } })
.query(async ({ input, ctx }) => {
// Only admins can view other users details
if (ctx.session.user.id !== input.userId && !ctx.session.user.permissions.includes("admin")) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Invalid password",
message: "You are not allowed to view other users details",
});
}
}
const user = await ctx.db.query.users.findFirst({
columns: {
id: true,
name: true,
email: true,
emailVerified: true,
image: true,
provider: true,
homeBoardId: true,
firstDayOfWeek: true,
pingIconsEnabled: true,
},
where: eq(users.id, input.userId),
});
const salt = await createSaltAsync();
const hashedPassword = await hashPasswordAsync(input.password, salt);
await ctx.db
.update(users)
.set({
password: hashedPassword,
})
.where(eq(users.id, input.userId));
}),
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
return user;
}),
editProfile: protectedProcedure
.input(validation.user.editProfile)
.output(z.void())
.meta({ openapi: { method: "PUT", path: "/api/users/profile", tags: ["users"], protect: true } })
.mutation(async ({ input, ctx }) => {
// Only admins can view other users details
if (ctx.session.user.id !== input.id && !ctx.session.user.permissions.includes("admin")) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You are not allowed to edit other users details",
});
}
const user = await ctx.db.query.users.findFirst({
columns: { email: true, provider: true },
where: eq(users.id, input.id),
});
if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
if (user.provider !== "credentials") {
throw new TRPCError({
code: "FORBIDDEN",
message: "Username and email can not be changed for users with external providers",
});
}
await checkUsernameAlreadyTakenAndThrowAsync(ctx.db, "credentials", input.name, input.id);
const emailDirty = input.email && user.email !== input.email;
await ctx.db
.update(users)
.set({
name: input.name,
email: emailDirty === true ? input.email : undefined,
emailVerified: emailDirty === true ? null : undefined,
})
.where(eq(users.id, input.id));
}),
delete: protectedProcedure
.input(z.object({ userId: z.string() }))
.output(z.void())
.meta({ openapi: { method: "DELETE", path: "/api/users/{userId}", tags: ["users"], protect: true } })
.mutation(async ({ input, ctx }) => {
// Only admins and user itself can delete a user
if (ctx.session.user.id !== input.userId && !ctx.session.user.permissions.includes("admin")) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You are not allowed to delete other users",
});
}
await ctx.db.delete(users).where(eq(users.id, input.userId));
}),
changePassword: protectedProcedure
.input(validation.user.changePasswordApi)
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/{userId}/changePassword", tags: ["users"], protect: true } })
.mutation(async ({ ctx, input }) => {
const user = ctx.session.user;
// Only admins can change other users' passwords
if (!user.permissions.includes("admin") && user.id !== input.userId) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
const dbUser = await ctx.db.query.users.findFirst({
columns: {
id: true,
password: true,
salt: true,
provider: true,
},
where: eq(users.id, input.userId),
});
if (!dbUser) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
if (dbUser.provider !== "credentials") {
throw new TRPCError({
code: "FORBIDDEN",
message: "Password can not be changed for users with external providers",
});
}
// Admins can change the password of other users without providing the previous password
const isPreviousPasswordRequired = ctx.session.user.id === input.userId;
logger.info(
`User ${user.id} is changing password for user ${input.userId}, previous password is required: ${isPreviousPasswordRequired}`,
);
if (isPreviousPasswordRequired) {
const previousPasswordHash = await hashPasswordAsync(input.previousPassword, dbUser.salt ?? "");
const isValid = previousPasswordHash === dbUser.password;
if (!isValid) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Invalid password",
});
}
}
const salt = await createSaltAsync();
const hashedPassword = await hashPasswordAsync(input.password, salt);
await ctx.db
.update(users)
.set({
password: hashedPassword,
})
.where(eq(users.id, input.userId));
}),
changeHomeBoardId: protectedProcedure
.input(validation.user.changeHomeBoard.and(z.object({ userId: z.string() })))
.input(convertIntersectionToZodObject(validation.user.changeHomeBoard.and(z.object({ userId: z.string() }))))
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/changeHome", tags: ["users"], protect: true } })
.mutation(async ({ input, ctx }) => {
const user = ctx.session.user;
// Only admins can change other users passwords
@@ -373,14 +406,18 @@ export const userRouter = createTRPCRouter({
})
.where(eq(users.id, input.userId));
}),
changeColorScheme: protectedProcedure.input(validation.user.changeColorScheme).mutation(async ({ input, ctx }) => {
await ctx.db
.update(users)
.set({
colorScheme: input.colorScheme,
})
.where(eq(users.id, ctx.session.user.id));
}),
changeColorScheme: protectedProcedure
.input(validation.user.changeColorScheme)
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/changeScheme", tags: ["users"], protect: true } })
.mutation(async ({ input, ctx }) => {
await ctx.db
.update(users)
.set({
colorScheme: input.colorScheme,
})
.where(eq(users.id, ctx.session.user.id));
}),
getPingIconsEnabledOrDefault: publicProcedure.query(async ({ ctx }) => {
if (!ctx.session?.user) {
return false;
@@ -414,7 +451,7 @@ export const userRouter = createTRPCRouter({
})
.where(eq(users.id, ctx.session.user.id));
}),
getFirstDayOfWeekForUserOrDefault: publicProcedure.query(async ({ ctx }) => {
getFirstDayOfWeekForUserOrDefault: publicProcedure.input(z.undefined()).query(async ({ ctx }) => {
if (!ctx.session?.user) {
return 1 as const;
}
@@ -430,7 +467,9 @@ export const userRouter = createTRPCRouter({
return user?.firstDayOfWeek ?? (1 as const);
}),
changeFirstDayOfWeek: protectedProcedure
.input(validation.user.firstDayOfWeek.and(validation.common.byId))
.input(convertIntersectionToZodObject(validation.user.firstDayOfWeek.and(validation.common.byId)))
.output(z.void())
.meta({ openapi: { method: "PATCH", path: "/api/users/firstDayOfWeek", tags: ["users"], protect: true } })
.mutation(async ({ input, ctx }) => {
// Only admins can change other users first day of week
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) {

View File

@@ -0,0 +1,22 @@
import type { AnyZodObject, ZodIntersection, ZodObject } from "@homarr/validation";
import { z } from "@homarr/validation";
export function convertIntersectionToZodObject<TIntersection extends ZodIntersection<AnyZodObject, AnyZodObject>>(
intersection: TIntersection,
) {
const { _def } = intersection;
// Merge the shapes
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const mergedShape = { ..._def.left.shape, ..._def.right.shape };
// Return a new ZodObject
return z.object(mergedShape) as unknown as TIntersection extends ZodIntersection<infer TLeft, infer TRight>
? TLeft extends AnyZodObject
? TRight extends AnyZodObject
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
ZodObject<TLeft["shape"] & TRight["shape"], any, any, z.infer<TLeft> & z.infer<TRight>>
: never
: never
: never;
}

View File

@@ -8,7 +8,7 @@
*/
import { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson";
import type { OpenApiMeta } from "trpc-swagger";
import type { OpenApiMeta } from "trpc-to-openapi";
import type { Session } from "@homarr/auth";
import { FlattenError } from "@homarr/common";

View File

@@ -9,7 +9,8 @@
"./client": "./client.ts",
"./schema/sqlite": "./schema/sqlite.ts",
"./test": "./test/index.ts",
"./queries": "./queries/index.ts"
"./queries": "./queries/index.ts",
"./validationSchemas": "./validationSchemas.ts"
},
"main": "./index.ts",
"types": "./index.ts",
@@ -46,6 +47,7 @@
"dotenv": "^16.4.5",
"drizzle-kit": "^0.28.1",
"drizzle-orm": "^0.36.4",
"drizzle-zod": "^0.5.1",
"mysql2": "3.11.4"
},
"devDependencies": {

View File

@@ -0,0 +1,11 @@
import { createSelectSchema } from "drizzle-zod";
import { apps, boards, groups, invites, searchEngines, serverSettings, users } from "./schema/sqlite";
export const selectAppSchema = createSelectSchema(apps);
export const selectBoardSchema = createSelectSchema(boards);
export const selectGroupSchema = createSelectSchema(groups);
export const selectInviteSchema = createSelectSchema(invites);
export const selectSearchEnginesSchema = createSelectSchema(searchEngines);
export const selectSeverSettingsSchema = createSelectSchema(serverSettings);
export const selectUserSchema = createSelectSchema(users);

File diff suppressed because it is too large Load Diff

199
pnpm-lock.yaml generated
View File

@@ -4,11 +4,6 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
trpc-swagger@1.2.6:
hash: 6s72z7zx33c52iesv5sewipn6i
path: patches/trpc-swagger@1.2.6.patch
importers:
.:
@@ -24,7 +19,7 @@ importers:
version: 4.3.3(vite@5.4.5(@types/node@22.9.3)(sass@1.81.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))
'@vitest/coverage-v8':
specifier: ^2.1.5
version: 2.1.5(vitest@2.1.5)
version: 2.1.5(vitest@2.1.5(@types/node@22.9.3)(@vitest/ui@2.1.5)(jsdom@25.0.1)(sass@1.81.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))
'@vitest/ui':
specifier: ^2.1.5
version: 2.1.5(vitest@2.1.5)
@@ -527,9 +522,9 @@ importers:
superjson:
specifier: 2.2.1
version: 2.2.1
trpc-swagger:
specifier: ^1.2.6
version: 1.2.6(patch_hash=6s72z7zx33c52iesv5sewipn6i)(@trpc/client@11.0.0-rc.643(@trpc/server@11.0.0-rc.643(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.0.0-rc.643(typescript@5.7.2))(zod@3.23.8)
trpc-to-openapi:
specifier: ^2.0.2
version: 2.0.2(@trpc/server@11.0.0-rc.643(typescript@5.7.2))(@types/express@4.17.21)(@types/node@22.9.3)(zod@3.23.8)
devDependencies:
'@homarr/eslint-config':
specifier: workspace:^0.2.0
@@ -877,6 +872,9 @@ importers:
drizzle-orm:
specifier: ^0.36.4
version: 0.36.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.12)(@types/react@18.3.12)(better-sqlite3@11.5.0)(mysql2@3.11.4)(react@18.3.1)
drizzle-zod:
specifier: ^0.5.1
version: 0.5.1(drizzle-orm@0.36.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.12)(@types/react@18.3.12)(better-sqlite3@11.5.0)(mysql2@3.11.4)(react@18.3.1))(zod@3.23.8)
mysql2:
specifier: 3.11.4
version: 3.11.4
@@ -4444,9 +4442,6 @@ packages:
resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==}
engines: {node: '>=12'}
chalk-scripts@1.2.8:
resolution: {integrity: sha512-Mu3mEn4lbqJHZD+wqBE8kwGb1TaNgcMspDIZVDzDHxKhK1zB3Q8q49PP15z0CNNDu5wxSwxhdPV+8UcejOSWxA==}
chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@@ -4459,10 +4454,6 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
chalk@5.3.0:
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
change-case@3.1.0:
resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==}
@@ -4616,6 +4607,10 @@ packages:
resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==}
engines: {node: '>=8'}
consola@3.2.3:
resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==}
engines: {node: ^14.18.0 || >=16.10.0}
console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
@@ -4629,6 +4624,9 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cookie-es@1.2.2:
resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
cookie@0.7.1:
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
engines: {node: '>= 0.6'}
@@ -4697,6 +4695,9 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
crossws@0.3.1:
resolution: {integrity: sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==}
crypto-random-string@2.0.0:
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
engines: {node: '>=8'}
@@ -4792,6 +4793,9 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
degenerator@5.0.1:
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
engines: {node: '>= 14'}
@@ -5006,6 +5010,12 @@ packages:
sqlite3:
optional: true
drizzle-zod@0.5.1:
resolution: {integrity: sha512-C/8bvzUH/zSnVfwdSibOgFjLhtDtbKYmkbPbUCq46QZyZCH6kODIMSOgZ8R7rVjoI+tCj3k06MRJMDqsIeoS4A==}
peerDependencies:
drizzle-orm: '>=0.23.13'
zod: '*'
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@@ -5552,6 +5562,9 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
h3@1.13.0:
resolution: {integrity: sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==}
handlebars@4.7.8:
resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
engines: {node: '>=0.4.7'}
@@ -5728,6 +5741,9 @@ packages:
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
engines: {node: '>= 12'}
iron-webcrypto@1.2.1:
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
is-alphabetical@1.0.4:
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
@@ -6233,6 +6249,11 @@ packages:
engines: {node: '>=4'}
hasBin: true
mime@3.0.0:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
hasBin: true
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -6463,9 +6484,17 @@ packages:
peerDependencies:
webpack: ^5.0.0
node-mocks-http@1.16.0:
resolution: {integrity: sha512-jmDjsr87ugnZ4nqBeX8ccMB1Fn04qc5Fz45XgrneJerWGV0VqS+wpu/zVkwv8LDAYHljDy5FzNvRJaOzEW9Dyw==}
node-mocks-http@1.16.1:
resolution: {integrity: sha512-Q2m5bmIE1KFeeKI6OsSn+c4XDara5NWnUJgzqnIkhiCNukYX+fqu0ADSeKOlpWtbCwgRnJ69F+7RUiQltzTKXA==}
engines: {node: '>=14'}
peerDependencies:
'@types/express': ^4.17.21 || ^5.0.0
'@types/node': '*'
peerDependenciesMeta:
'@types/express':
optional: true
'@types/node':
optional: true
node-plop@0.26.3:
resolution: {integrity: sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q==}
@@ -6535,6 +6564,9 @@ packages:
ofetch@1.4.1:
resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==}
ohash@1.1.4:
resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -6553,8 +6585,8 @@ packages:
resolution: {integrity: sha512-dtyTFKx2xVcO0W8JKaluXIHC9l/MLjHeflBaWjiWNMCHp/TBs9dEjQDbj/VFlHR4omFOKjjmqm1pW1aCAhmPBg==}
engines: {node: '>=12.20.0'}
openapi-types@12.1.3:
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
openapi3-ts@4.3.3:
resolution: {integrity: sha512-LKkzBGJcZ6wdvkKGMoSvpK+0cbN5Xc3XuYkJskO+vjEQWJgs1kgtyUk0pjf8KwPuysv323Er62F5P17XQl96Qg==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
@@ -6662,9 +6694,6 @@ packages:
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
engines: {node: '>= 14.16'}
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
@@ -6879,8 +6908,8 @@ packages:
resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==}
engines: {node: '>=8'}
qs@6.13.0:
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
qs@6.13.1:
resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==}
engines: {node: '>=0.6'}
querystringify@2.2.0:
@@ -6892,6 +6921,9 @@ packages:
queue-tick@1.0.1:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
radix3@1.1.2:
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
ramda-adjunct@5.1.0:
resolution: {integrity: sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==}
engines: {node: '>=0.10.3'}
@@ -7711,12 +7743,11 @@ packages:
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
engines: {node: '>= 14.0.0'}
trpc-swagger@1.2.6:
resolution: {integrity: sha512-LVh2NicwYZdaUEvshY9IF1oL02z9PWjltY0CwTslHw4mi4DcSAP4bx/FPfp5+371oj75vujjNbOjGG9grNl3Xg==}
trpc-to-openapi@2.0.2:
resolution: {integrity: sha512-uuBkemnf9SY0mRv/wo0BBogdaoReOEg4zk8CSu95yUVgl/DxtDs+Ie02XGcGr1fCYKiyrfJbhRAo2wBbWV0POg==}
peerDependencies:
'@trpc/client': ^10.45.2
'@trpc/server': ^10.45.2
zod: ^3.14.4
'@trpc/server': ^11.0.0-rc.566
zod: ^3.23.8
ts-api-utils@1.3.0:
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
@@ -7887,6 +7918,9 @@ packages:
unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
uncrypto@0.1.3:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
@@ -7901,6 +7935,9 @@ packages:
resolution: {integrity: sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==}
engines: {node: '>=18.17'}
unenv@1.10.0:
resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==}
unique-string@2.0.0:
resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
engines: {node: '>=8'}
@@ -8029,10 +8066,6 @@ packages:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
@@ -8355,10 +8388,11 @@ packages:
peerDependencies:
zod: '>= 3.11.0'
zod-to-json-schema@3.23.3:
resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==}
zod-openapi@2.19.0:
resolution: {integrity: sha512-OUAAyBDPPwZ9u61i4k/LieXUzP2re8kFjqdNh2AvHjsyi/aRNz9leDAtMGcSoSzUT5xUeQoACJufBI6FzzZyxA==}
engines: {node: '>=16.11'}
peerDependencies:
zod: ^3.23.3
zod: ^3.21.4
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
@@ -10623,7 +10657,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@vitest/coverage-v8@2.1.5(vitest@2.1.5)':
'@vitest/coverage-v8@2.1.5(vitest@2.1.5(@types/node@22.9.3)(@vitest/ui@2.1.5)(jsdom@25.0.1)(sass@1.81.0)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))':
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 0.2.3
@@ -11185,12 +11219,6 @@ snapshots:
loupe: 3.1.2
pathval: 2.0.0
chalk-scripts@1.2.8:
dependencies:
chalk: 5.3.0
performance-now: 2.1.0
uuid: 9.0.1
chalk@2.4.2:
dependencies:
ansi-styles: 3.2.1
@@ -11207,8 +11235,6 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
chalk@5.3.0: {}
change-case@3.1.0:
dependencies:
camel-case: 3.0.0
@@ -11295,7 +11321,7 @@ snapshots:
dependencies:
'@hapi/bourne': 3.0.0
inflation: 2.1.0
qs: 6.13.0
qs: 6.13.1
raw-body: 2.5.2
type-is: 1.6.18
@@ -11372,6 +11398,8 @@ snapshots:
write-file-atomic: 3.0.3
xdg-basedir: 4.0.0
consola@3.2.3: {}
console-control-strings@1.1.0: {}
constant-case@2.0.0:
@@ -11385,6 +11413,8 @@ snapshots:
convert-source-map@2.0.0: {}
cookie-es@1.2.2: {}
cookie@0.7.1: {}
cookie@0.7.2: {}
@@ -11453,6 +11483,10 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
crossws@0.3.1:
dependencies:
uncrypto: 0.1.3
crypto-random-string@2.0.0: {}
css.escape@1.5.1: {}
@@ -11532,6 +11566,8 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
defu@6.1.4: {}
degenerator@5.0.1:
dependencies:
ast-types: 0.13.4
@@ -11670,6 +11706,11 @@ snapshots:
mysql2: 3.11.4
react: 18.3.1
drizzle-zod@0.5.1(drizzle-orm@0.36.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.12)(@types/react@18.3.12)(better-sqlite3@11.5.0)(mysql2@3.11.4)(react@18.3.1))(zod@3.23.8):
dependencies:
drizzle-orm: 0.36.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.12)(@types/react@18.3.12)(better-sqlite3@11.5.0)(mysql2@3.11.4)(react@18.3.1)
zod: 3.23.8
eastasianwidth@0.2.0: {}
effect@3.9.2: {}
@@ -12448,6 +12489,19 @@ snapshots:
graphemer@1.4.0: {}
h3@1.13.0:
dependencies:
cookie-es: 1.2.2
crossws: 0.3.1
defu: 6.1.4
destr: 2.0.3
iron-webcrypto: 1.2.1
ohash: 1.1.4
radix3: 1.1.2
ufo: 1.5.4
uncrypto: 0.1.3
unenv: 1.10.0
handlebars@4.7.8:
dependencies:
minimist: 1.2.8
@@ -12657,6 +12711,8 @@ snapshots:
jsbn: 1.1.0
sprintf-js: 1.1.3
iron-webcrypto@1.2.1: {}
is-alphabetical@1.0.4: {}
is-alphanumerical@1.0.4:
@@ -13147,6 +13203,8 @@ snapshots:
mime@1.6.0: {}
mime@3.0.0: {}
mimic-fn@2.1.0: {}
mimic-response@3.1.0: {}
@@ -13374,10 +13432,8 @@ snapshots:
loader-utils: 2.0.4
webpack: 5.94.0
node-mocks-http@1.16.0:
node-mocks-http@1.16.1(@types/express@4.17.21)(@types/node@22.9.3):
dependencies:
'@types/express': 4.17.21
'@types/node': 22.9.3
accepts: 1.3.8
content-disposition: 0.5.4
depd: 1.1.2
@@ -13388,6 +13444,9 @@ snapshots:
parseurl: 1.3.3
range-parser: 1.2.1
type-is: 1.6.18
optionalDependencies:
'@types/express': 4.17.21
'@types/node': 22.9.3
node-plop@0.26.3:
dependencies:
@@ -13472,6 +13531,8 @@ snapshots:
node-fetch-native: 1.6.4
ufo: 1.5.4
ohash@1.1.4: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
@@ -13492,7 +13553,9 @@ snapshots:
dependencies:
apg-lite: 1.0.4
openapi-types@12.1.3: {}
openapi3-ts@4.3.3:
dependencies:
yaml: 2.5.1
optionator@0.9.4:
dependencies:
@@ -13622,8 +13685,6 @@ snapshots:
pathval@2.0.0: {}
performance-now@2.1.0: {}
picocolors@1.0.1: {}
picocolors@1.1.0: {}
@@ -13886,7 +13947,7 @@ snapshots:
dependencies:
escape-goat: 2.1.1
qs@6.13.0:
qs@6.13.1:
dependencies:
side-channel: 1.0.6
@@ -13896,6 +13957,8 @@ snapshots:
queue-tick@1.0.1: {}
radix3@1.1.2: {}
ramda-adjunct@5.1.0(ramda@0.30.1):
dependencies:
ramda: 0.30.1
@@ -14863,17 +14926,19 @@ snapshots:
triple-beam@1.4.1: {}
trpc-swagger@1.2.6(patch_hash=6s72z7zx33c52iesv5sewipn6i)(@trpc/client@11.0.0-rc.643(@trpc/server@11.0.0-rc.643(typescript@5.7.2))(typescript@5.7.2))(@trpc/server@11.0.0-rc.643(typescript@5.7.2))(zod@3.23.8):
trpc-to-openapi@2.0.2(@trpc/server@11.0.0-rc.643(typescript@5.7.2))(@types/express@4.17.21)(@types/node@22.9.3)(zod@3.23.8):
dependencies:
'@trpc/client': 11.0.0-rc.643(@trpc/server@11.0.0-rc.643(typescript@5.7.2))(typescript@5.7.2)
'@trpc/server': 11.0.0-rc.643(typescript@5.7.2)
chalk-scripts: 1.2.8
co-body: 6.2.0
h3: 1.13.0
lodash.clonedeep: 4.5.0
node-mocks-http: 1.16.0
openapi-types: 12.1.3
node-mocks-http: 1.16.1(@types/express@4.17.21)(@types/node@22.9.3)
openapi3-ts: 4.3.3
zod: 3.23.8
zod-to-json-schema: 3.23.3(zod@3.23.8)
zod-openapi: 2.19.0(zod@3.23.8)
transitivePeerDependencies:
- '@types/express'
- '@types/node'
ts-api-utils@1.3.0(typescript@5.7.2):
dependencies:
@@ -15042,6 +15107,8 @@ snapshots:
has-symbols: 1.0.3
which-boxed-primitive: 1.0.2
uncrypto@0.1.3: {}
undici-types@5.26.5: {}
undici-types@6.19.8: {}
@@ -15052,6 +15119,14 @@ snapshots:
undici@6.21.0: {}
unenv@1.10.0:
dependencies:
consola: 3.2.3
defu: 6.1.4
mime: 3.0.0
node-fetch-native: 1.6.4
pathe: 1.1.2
unique-string@2.0.0:
dependencies:
crypto-random-string: 2.0.0
@@ -15178,8 +15253,6 @@ snapshots:
uuid@8.3.2: {}
uuid@9.0.1: {}
v8-compile-cache-lib@3.0.1: {}
validate-npm-package-name@5.0.1: {}
@@ -15535,7 +15608,7 @@ snapshots:
dependencies:
zod: 3.23.8
zod-to-json-schema@3.23.3(zod@3.23.8):
zod-openapi@2.19.0(zod@3.23.8):
dependencies:
zod: 3.23.8