mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-26 16:30:57 +01:00
@@ -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) => {
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { generateOpenApiDocument } from "trpc-swagger";
|
||||
import { generateOpenApiDocument } from "trpc-to-openapi";
|
||||
|
||||
import { appRouter } from "./root";
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
22
packages/api/src/schema-merger.ts
Normal file
22
packages/api/src/schema-merger.ts
Normal 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;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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": {
|
||||
|
||||
11
packages/db/validationSchemas.ts
Normal file
11
packages/db/validationSchemas.ts
Normal 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
199
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user