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"; "use client";
import type { OpenAPIV3 } from "openapi-types"; import type { OpenAPIObject } from "openapi3-ts/oas31";
import SwaggerUI from "swagger-ui-react"; 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 // 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"; import "../swagger-ui.css";
interface SwaggerUIClientProps { interface SwaggerUIClientProps {
document: OpenAPIV3.Document; document: OpenAPIObject;
} }
export const SwaggerUIClient = ({ document }: SwaggerUIClientProps) => { 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 }), children: t("user.action.delete.confirm", { username: user.name }),
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
async onConfirm() { async onConfirm() {
await mutateUserDeletionAsync(user.id); await mutateUserDeletionAsync({
userId: user.id,
});
}, },
}), }),
[user, mutateUserDeletionAsync, openConfirmModal, t], [user, mutateUserDeletionAsync, openConfirmModal, t],

View File

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

View File

@@ -1,7 +1,7 @@
import { headers } from "next/headers"; import { headers } from "next/headers";
import { userAgent } from "next/server"; import { userAgent } from "next/server";
import type { NextRequest } 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 { appRouter, createTRPCContext } from "@homarr/api";
import { hashPasswordAsync } from "@homarr/auth"; import { hashPasswordAsync } from "@homarr/auth";

View File

@@ -45,10 +45,5 @@
"packageManager": "pnpm@9.14.2", "packageManager": "pnpm@9.14.2",
"engines": { "engines": {
"node": ">=22.11.0" "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", "next": "^14.2.18",
"react": "^18.3.1", "react": "^18.3.1",
"superjson": "2.2.1", "superjson": "2.2.1",
"trpc-swagger": "^1.2.6" "trpc-to-openapi": "^2.0.2"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@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"; 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 { asc, createId, eq, inArray, like } from "@homarr/db";
import { apps } from "@homarr/db/schema/sqlite"; import { apps } from "@homarr/db/schema/sqlite";
import { selectAppSchema } from "@homarr/db/validationSchemas";
import { validation, z } from "@homarr/validation"; import { validation, z } from "@homarr/validation";
import { convertIntersectionToZodObject } from "../schema-merger";
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc"; import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
import { canUserSeeAppAsync } from "./app/app-access-control"; import { canUserSeeAppAsync } from "./app/app-access-control";
export const appRouter = createTRPCRouter({ export const appRouter = createTRPCRouter({
all: protectedProcedure all: protectedProcedure
.input(z.void()) .input(z.void())
.output( .output(z.array(selectAppSchema))
z.array(
z.object({
name: z.string(),
id: z.string(),
description: z.string().nullable(),
iconUrl: z.string(),
href: z.string().nullable(),
}),
),
)
.meta({ openapi: { method: "GET", path: "/api/apps", tags: ["apps"], protect: true } }) .meta({ openapi: { method: "GET", path: "/api/apps", tags: ["apps"], protect: true } })
.query(({ ctx }) => { .query(({ ctx }) => {
return ctx.db.query.apps.findMany({ return ctx.db.query.apps.findMany({
@@ -29,17 +21,7 @@ export const appRouter = createTRPCRouter({
}), }),
search: protectedProcedure search: protectedProcedure
.input(z.object({ query: z.string(), limit: z.number().min(1).max(100).default(10) })) .input(z.object({ query: z.string(), limit: z.number().min(1).max(100).default(10) }))
.output( .output(z.array(selectAppSchema))
z.array(
z.object({
name: z.string(),
id: z.string(),
description: z.string().nullable(),
iconUrl: z.string(),
href: z.string().nullable(),
}),
),
)
.meta({ openapi: { method: "GET", path: "/api/apps/search", tags: ["apps"], protect: true } }) .meta({ openapi: { method: "GET", path: "/api/apps/search", tags: ["apps"], protect: true } })
.query(({ ctx, input }) => { .query(({ ctx, input }) => {
return ctx.db.query.apps.findMany({ return ctx.db.query.apps.findMany({
@@ -50,17 +32,7 @@ export const appRouter = createTRPCRouter({
}), }),
selectable: protectedProcedure selectable: protectedProcedure
.input(z.void()) .input(z.void())
.output( .output(z.array(selectAppSchema.pick({ id: true, name: true, iconUrl: true, href: true, description: true })))
z.array(
z.object({
name: z.string(),
id: z.string(),
iconUrl: z.string(),
description: z.string().nullable(),
href: z.string().nullable(),
}),
),
)
.meta({ .meta({
openapi: { openapi: {
method: "GET", method: "GET",
@@ -83,15 +55,7 @@ export const appRouter = createTRPCRouter({
}), }),
byId: publicProcedure byId: publicProcedure
.input(validation.common.byId) .input(validation.common.byId)
.output( .output(selectAppSchema)
z.object({
name: z.string(),
id: z.string(),
description: z.string().nullable(),
iconUrl: z.string(),
href: z.string().nullable(),
}),
)
.meta({ openapi: { method: "GET", path: "/api/apps/{id}", tags: ["apps"], protect: true } }) .meta({ openapi: { method: "GET", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const app = await ctx.db.query.apps.findFirst({ const app = await ctx.db.query.apps.findFirst({
@@ -136,7 +100,9 @@ export const appRouter = createTRPCRouter({
}), }),
update: permissionRequiredProcedure update: permissionRequiredProcedure
.requiresPermission("app-modify-all") .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 }) => { .mutation(async ({ ctx, input }) => {
const app = await ctx.db.query.apps.findFirst({ const app = await ctx.db.query.apps.findFirst({
where: eq(apps.id, input.id), 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 { asc, createId, eq } from "@homarr/db";
import { invites } from "@homarr/db/schema/sqlite"; import { invites } from "@homarr/db/schema/sqlite";
import { selectInviteSchema } from "@homarr/db/validationSchemas";
import { z } from "@homarr/validation"; import { z } from "@homarr/validation";
import { createTRPCRouter, protectedProcedure } from "../trpc"; import { createTRPCRouter, protectedProcedure } from "../trpc";
import { throwIfCredentialsDisabled } from "./invite/checks"; import { throwIfCredentialsDisabled } from "./invite/checks";
export const inviteRouter = createTRPCRouter({ export const inviteRouter = createTRPCRouter({
getAll: protectedProcedure.query(async ({ ctx }) => { getAll: protectedProcedure
throwIfCredentialsDisabled(); .output(
const dbInvites = await ctx.db.query.invites.findMany({ z.array(
orderBy: asc(invites.expirationDate), selectInviteSchema
columns: { .pick({
token: false,
},
with: {
creator: {
columns: {
id: true, 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 createInvite: protectedProcedure
.input( .input(
z.object({ z.object({
expirationDate: z.date(), 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 }) => { .mutation(async ({ ctx, input }) => {
throwIfCredentialsDisabled(); throwIfCredentialsDisabled();
const id = createId(); const id = createId();
@@ -56,6 +71,8 @@ export const inviteRouter = createTRPCRouter({
id: z.string(), id: z.string(),
}), }),
) )
.output(z.undefined())
.meta({ openapi: { method: "DELETE", path: "/api/invites/{id}", tags: ["invites"], protect: true } })
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
throwIfCredentialsDisabled(); throwIfCredentialsDisabled();
const dbInvite = await ctx.db.query.invites.findFirst({ 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 db.insert(schema.users).values(initialUsers);
await caller.delete(defaultOwnerId); await caller.delete({ userId: defaultOwnerId });
const usersInDb = await db.select().from(schema.users); const usersInDb = await db.select().from(schema.users);
expect(usersInDb).toHaveLength(2); expect(usersInDb).toHaveLength(2);

View File

@@ -4,10 +4,12 @@ import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, createId, eq, like, schema } from "@homarr/db"; import { and, createId, eq, like, schema } from "@homarr/db";
import { groupMembers, groupPermissions, groups, invites, users } from "@homarr/db/schema/sqlite"; import { groupMembers, groupPermissions, groups, invites, users } from "@homarr/db/schema/sqlite";
import { selectUserSchema } from "@homarr/db/validationSchemas";
import type { SupportedAuthProvider } from "@homarr/definitions"; import type { SupportedAuthProvider } from "@homarr/definitions";
import { logger } from "@homarr/log"; import { logger } from "@homarr/log";
import { validation, z } from "@homarr/validation"; import { validation, z } from "@homarr/validation";
import { convertIntersectionToZodObject } from "../schema-merger";
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc"; import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
import { throwIfCredentialsDisabled } from "./invite/checks"; import { throwIfCredentialsDisabled } from "./invite/checks";
@@ -44,31 +46,34 @@ export const userRouter = createTRPCRouter({
userId, userId,
}); });
}), }),
register: publicProcedure.input(validation.user.registrationApi).mutation(async ({ ctx, input }) => { register: publicProcedure
throwIfCredentialsDisabled(); .input(validation.user.registrationApi)
const inviteWhere = and(eq(invites.id, input.inviteId), eq(invites.token, input.token)); .output(z.void())
const dbInvite = await ctx.db.query.invites.findFirst({ .mutation(async ({ ctx, input }) => {
columns: { throwIfCredentialsDisabled();
id: true, const inviteWhere = and(eq(invites.id, input.inviteId), eq(invites.token, input.token));
expirationDate: true, const dbInvite = await ctx.db.query.invites.findFirst({
}, columns: {
where: inviteWhere, id: true,
}); expirationDate: true,
},
if (!dbInvite || dbInvite.expirationDate < new Date()) { where: inviteWhere,
throw new TRPCError({
code: "FORBIDDEN",
message: "Invalid invite",
}); });
}
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 createUserAsync(ctx.db, input);
await ctx.db.delete(invites).where(inviteWhere);
}), // Delete invite as it's used
await ctx.db.delete(invites).where(inviteWhere);
}),
create: permissionRequiredProcedure create: permissionRequiredProcedure
.requiresPermission("admin") .requiresPermission("admin")
.meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } }) .meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } })
@@ -85,6 +90,8 @@ export const userRouter = createTRPCRouter({
} }
}), }),
setProfileImage: protectedProcedure setProfileImage: protectedProcedure
.output(z.void())
.meta({ openapi: { method: "PUT", path: "/api/users/profileImage", tags: ["users"], protect: true } })
.input( .input(
z.object({ z.object({
userId: z.string(), userId: z.string(),
@@ -138,17 +145,7 @@ export const userRouter = createTRPCRouter({
getAll: permissionRequiredProcedure getAll: permissionRequiredProcedure
.requiresPermission("admin") .requiresPermission("admin")
.input(z.void()) .input(z.void())
.output( .output(z.array(selectUserSchema.pick({ id: true, name: true, email: true, emailVerified: true, image: true })))
z.array(
z.object({
id: z.string(),
name: z.string().nullable(),
email: z.string().nullable(),
emailVerified: z.date().nullable(),
image: z.string().nullable(),
}),
),
)
.meta({ openapi: { method: "GET", path: "/api/users", tags: ["users"], protect: true } }) .meta({ openapi: { method: "GET", path: "/api/users", tags: ["users"], protect: true } })
.query(({ ctx }) => { .query(({ ctx }) => {
return ctx.db.query.users.findMany({ 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 // Is protected because also used in board access / integration access forms
selectable: protectedProcedure.query(({ ctx }) => { selectable: protectedProcedure
return ctx.db.query.users.findMany({ .input(z.undefined())
columns: { .output(z.array(selectUserSchema.pick({ id: true, name: true, image: true })))
id: true, .meta({ openapi: { method: "GET", path: "/api/users/selectable", tags: ["users"], protect: true } })
name: true, .query(({ ctx }) => {
image: true, return ctx.db.query.users.findMany({
}, columns: {
}); id: true,
}), name: true,
image: true,
},
});
}),
search: permissionRequiredProcedure search: permissionRequiredProcedure
.requiresPermission("admin") .requiresPermission("admin")
.input( .input(
@@ -179,6 +180,8 @@ export const userRouter = createTRPCRouter({
limit: z.number().min(1).max(100).default(10), 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 }) => { .query(async ({ input, ctx }) => {
const dbUsers = await ctx.db.query.users.findMany({ const dbUsers = await ctx.db.query.users.findMany({
columns: { columns: {
@@ -195,16 +198,10 @@ export const userRouter = createTRPCRouter({
image: user.image, image: user.image,
})); }));
}), }),
getById: protectedProcedure.input(z.object({ userId: z.string() })).query(async ({ input, ctx }) => { getById: protectedProcedure
// Only admins can view other users details .input(z.object({ userId: z.string() }))
if (ctx.session.user.id !== input.userId && !ctx.session.user.permissions.includes("admin")) { .output(
throw new TRPCError({ selectUserSchema.pick({
code: "FORBIDDEN",
message: "You are not allowed to view other users details",
});
}
const user = await ctx.db.query.users.findFirst({
columns: {
id: true, id: true,
name: true, name: true,
email: true, email: true,
@@ -214,134 +211,170 @@ export const userRouter = createTRPCRouter({
homeBoardId: true, homeBoardId: true,
firstDayOfWeek: true, firstDayOfWeek: true,
pingIconsEnabled: true, pingIconsEnabled: true,
}, }),
where: eq(users.id, input.userId), )
}); .meta({ openapi: { method: "GET", path: "/api/users/{userId}", tags: ["users"], protect: true } })
.query(async ({ input, ctx }) => {
if (!user) { // Only admins can view other users details
throw new TRPCError({ if (ctx.session.user.id !== input.userId && !ctx.session.user.permissions.includes("admin")) {
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) {
throw new TRPCError({ throw new TRPCError({
code: "FORBIDDEN", 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(); if (!user) {
const hashedPassword = await hashPasswordAsync(input.password, salt); throw new TRPCError({
await ctx.db code: "NOT_FOUND",
.update(users) message: "User not found",
.set({ });
password: hashedPassword, }
})
.where(eq(users.id, input.userId)); 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 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 }) => { .mutation(async ({ input, ctx }) => {
const user = ctx.session.user; const user = ctx.session.user;
// Only admins can change other users passwords // Only admins can change other users passwords
@@ -373,14 +406,18 @@ export const userRouter = createTRPCRouter({
}) })
.where(eq(users.id, input.userId)); .where(eq(users.id, input.userId));
}), }),
changeColorScheme: protectedProcedure.input(validation.user.changeColorScheme).mutation(async ({ input, ctx }) => { changeColorScheme: protectedProcedure
await ctx.db .input(validation.user.changeColorScheme)
.update(users) .output(z.void())
.set({ .meta({ openapi: { method: "PATCH", path: "/api/users/changeScheme", tags: ["users"], protect: true } })
colorScheme: input.colorScheme, .mutation(async ({ input, ctx }) => {
}) await ctx.db
.where(eq(users.id, ctx.session.user.id)); .update(users)
}), .set({
colorScheme: input.colorScheme,
})
.where(eq(users.id, ctx.session.user.id));
}),
getPingIconsEnabledOrDefault: publicProcedure.query(async ({ ctx }) => { getPingIconsEnabledOrDefault: publicProcedure.query(async ({ ctx }) => {
if (!ctx.session?.user) { if (!ctx.session?.user) {
return false; return false;
@@ -414,7 +451,7 @@ export const userRouter = createTRPCRouter({
}) })
.where(eq(users.id, ctx.session.user.id)); .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) { if (!ctx.session?.user) {
return 1 as const; return 1 as const;
} }
@@ -430,7 +467,9 @@ export const userRouter = createTRPCRouter({
return user?.firstDayOfWeek ?? (1 as const); return user?.firstDayOfWeek ?? (1 as const);
}), }),
changeFirstDayOfWeek: protectedProcedure 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 }) => { .mutation(async ({ input, ctx }) => {
// Only admins can change other users first day of week // Only admins can change other users first day of week
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) { 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 { initTRPC, TRPCError } from "@trpc/server";
import superjson from "superjson"; import superjson from "superjson";
import type { OpenApiMeta } from "trpc-swagger"; import type { OpenApiMeta } from "trpc-to-openapi";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
import { FlattenError } from "@homarr/common"; import { FlattenError } from "@homarr/common";

View File

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