refactor: remove central validation export to improve typescript performance (#2810)
* refactor: remove central validation export to improve typescript performance * fix: missing package exports change in validation package * chore: address pull request feedback
This commit is contained in:
@@ -5,7 +5,8 @@ import { asc, createId, eq, inArray, like } from "@homarr/db";
|
||||
import { apps } from "@homarr/db/schema";
|
||||
import { selectAppSchema } from "@homarr/db/validationSchemas";
|
||||
import { getIconForName } from "@homarr/icons";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { appCreateManySchema, appEditSchema, appManageSchema } from "@homarr/validation/app";
|
||||
import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
|
||||
|
||||
import { convertIntersectionToZodObject } from "../schema-merger";
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
||||
@@ -15,7 +16,7 @@ const defaultIcon = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@mas
|
||||
|
||||
export const appRouter = createTRPCRouter({
|
||||
getPaginated: protectedProcedure
|
||||
.input(validation.common.paginated)
|
||||
.input(paginatedSchema)
|
||||
.output(z.object({ items: z.array(selectAppSchema), totalCount: z.number() }))
|
||||
.meta({ openapi: { method: "GET", path: "/api/apps/paginated", tags: ["apps"], protect: true } })
|
||||
.query(async ({ input, ctx }) => {
|
||||
@@ -83,7 +84,7 @@ export const appRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
byId: publicProcedure
|
||||
.input(validation.common.byId)
|
||||
.input(byIdSchema)
|
||||
.output(selectAppSchema)
|
||||
.meta({ openapi: { method: "GET", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
||||
.query(async ({ ctx, input }) => {
|
||||
@@ -115,7 +116,7 @@ export const appRouter = createTRPCRouter({
|
||||
}),
|
||||
create: permissionRequiredProcedure
|
||||
.requiresPermission("app-create")
|
||||
.input(validation.app.manage)
|
||||
.input(appManageSchema)
|
||||
.output(z.object({ appId: z.string() }))
|
||||
.meta({ openapi: { method: "POST", path: "/api/apps", tags: ["apps"], protect: true } })
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
@@ -133,7 +134,7 @@ export const appRouter = createTRPCRouter({
|
||||
}),
|
||||
createMany: permissionRequiredProcedure
|
||||
.requiresPermission("app-create")
|
||||
.input(validation.app.createMany)
|
||||
.input(appCreateManySchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await ctx.db.insert(apps).values(
|
||||
@@ -148,7 +149,7 @@ export const appRouter = createTRPCRouter({
|
||||
}),
|
||||
update: permissionRequiredProcedure
|
||||
.requiresPermission("app-modify-all")
|
||||
.input(convertIntersectionToZodObject(validation.app.edit))
|
||||
.input(convertIntersectionToZodObject(appEditSchema))
|
||||
.output(z.void())
|
||||
.meta({ openapi: { method: "PATCH", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
@@ -178,7 +179,7 @@ export const appRouter = createTRPCRouter({
|
||||
.requiresPermission("app-full-all")
|
||||
.output(z.void())
|
||||
.meta({ openapi: { method: "DELETE", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
||||
.input(validation.common.byId)
|
||||
.input(byIdSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await ctx.db.delete(apps).where(eq(apps.id, input.id));
|
||||
}),
|
||||
|
||||
@@ -37,8 +37,21 @@ import {
|
||||
import { importOldmarrAsync } from "@homarr/old-import";
|
||||
import { importJsonFileSchema } from "@homarr/old-import/shared";
|
||||
import { oldmarrConfigSchema } from "@homarr/old-schema";
|
||||
import type { BoardItemAdvancedOptions } from "@homarr/validation";
|
||||
import { sectionSchema, sharedItemSchema, validation, zodUnionFromArray } from "@homarr/validation";
|
||||
import {
|
||||
boardByNameSchema,
|
||||
boardChangeVisibilitySchema,
|
||||
boardCreateSchema,
|
||||
boardDuplicateSchema,
|
||||
boardRenameSchema,
|
||||
boardSaveLayoutsSchema,
|
||||
boardSavePartialSettingsSchema,
|
||||
boardSavePermissionsSchema,
|
||||
boardSaveSchema,
|
||||
} from "@homarr/validation/board";
|
||||
import { byIdSchema } from "@homarr/validation/common";
|
||||
import { zodUnionFromArray } from "@homarr/validation/enums";
|
||||
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
|
||||
import { sectionSchema, sharedItemSchema } from "@homarr/validation/shared";
|
||||
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
||||
import { throwIfActionForbiddenAsync } from "./board/board-access";
|
||||
@@ -247,7 +260,7 @@ export const boardRouter = createTRPCRouter({
|
||||
}),
|
||||
createBoard: permissionRequiredProcedure
|
||||
.requiresPermission("board-create")
|
||||
.input(validation.board.create)
|
||||
.input(boardCreateSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const boardId = createId();
|
||||
|
||||
@@ -291,7 +304,7 @@ export const boardRouter = createTRPCRouter({
|
||||
}),
|
||||
duplicateBoard: permissionRequiredProcedure
|
||||
.requiresPermission("board-create")
|
||||
.input(validation.board.duplicate)
|
||||
.input(boardDuplicateSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view");
|
||||
await noBoardWithSimilarNameAsync(ctx.db, input.name);
|
||||
@@ -506,34 +519,32 @@ export const boardRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
}),
|
||||
renameBoard: protectedProcedure.input(validation.board.rename).mutation(async ({ ctx, input }) => {
|
||||
renameBoard: protectedProcedure.input(boardRenameSchema).mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||
|
||||
await noBoardWithSimilarNameAsync(ctx.db, input.name, [input.id]);
|
||||
|
||||
await ctx.db.update(boards).set({ name: input.name }).where(eq(boards.id, input.id));
|
||||
}),
|
||||
changeBoardVisibility: protectedProcedure
|
||||
.input(validation.board.changeVisibility)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
|
||||
changeBoardVisibility: protectedProcedure.input(boardChangeVisibilitySchema).mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
|
||||
|
||||
if (
|
||||
input.visibility !== "public" &&
|
||||
(boardSettings.homeBoardId === input.id || boardSettings.mobileHomeBoardId === input.id)
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Cannot make home board private",
|
||||
});
|
||||
}
|
||||
if (
|
||||
input.visibility !== "public" &&
|
||||
(boardSettings.homeBoardId === input.id || boardSettings.mobileHomeBoardId === input.id)
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Cannot make home board private",
|
||||
});
|
||||
}
|
||||
|
||||
await ctx.db
|
||||
.update(boards)
|
||||
.set({ isPublic: input.visibility === "public" })
|
||||
.where(eq(boards.id, input.id));
|
||||
}),
|
||||
await ctx.db
|
||||
.update(boards)
|
||||
.set({ isPublic: input.visibility === "public" })
|
||||
.where(eq(boards.id, input.id));
|
||||
}),
|
||||
deleteBoard: protectedProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||
|
||||
@@ -572,13 +583,13 @@ export const boardRouter = createTRPCRouter({
|
||||
|
||||
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
||||
}),
|
||||
getBoardByName: publicProcedure.input(validation.board.byName).query(async ({ input, ctx }) => {
|
||||
getBoardByName: publicProcedure.input(boardByNameSchema).query(async ({ input, ctx }) => {
|
||||
const boardWhere = eq(sql`UPPER(${boards.name})`, input.name.toUpperCase());
|
||||
await throwIfActionForbiddenAsync(ctx, boardWhere, "view");
|
||||
|
||||
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
||||
}),
|
||||
saveLayouts: protectedProcedure.input(validation.board.saveLayouts).mutation(async ({ ctx, input }) => {
|
||||
saveLayouts: protectedProcedure.input(boardSaveLayoutsSchema).mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
||||
|
||||
const board = await getFullBoardWithWhereAsync(ctx.db, eq(boards.id, input.id), ctx.session.user.id);
|
||||
@@ -704,7 +715,7 @@ export const boardRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
savePartialBoardSettings: protectedProcedure
|
||||
.input(validation.board.savePartialSettings.and(z.object({ id: z.string() })))
|
||||
.input(boardSavePartialSettingsSchema.and(z.object({ id: z.string() })))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
||||
|
||||
@@ -738,7 +749,7 @@ export const boardRouter = createTRPCRouter({
|
||||
})
|
||||
.where(eq(boards.id, input.id));
|
||||
}),
|
||||
saveBoard: protectedProcedure.input(validation.board.save).mutation(async ({ input, ctx }) => {
|
||||
saveBoard: protectedProcedure.input(boardSaveSchema).mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
||||
|
||||
const dbBoard = await getFullBoardWithWhereAsync(ctx.db, eq(boards.id, input.id), ctx.session.user.id);
|
||||
@@ -1154,8 +1165,7 @@ export const boardRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
getBoardPermissions: protectedProcedure.input(validation.board.permissions).query(async ({ input, ctx }) => {
|
||||
getBoardPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||
|
||||
const dbGroupPermissions = await ctx.db.query.groupPermissions.findMany({
|
||||
@@ -1226,92 +1236,86 @@ export const boardRouter = createTRPCRouter({
|
||||
}),
|
||||
};
|
||||
}),
|
||||
saveUserBoardPermissions: protectedProcedure
|
||||
.input(validation.board.savePermissions)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
||||
saveUserBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
||||
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await db.transaction(async (transaction) => {
|
||||
await transaction
|
||||
.delete(schema.boardUserPermissions)
|
||||
.where(eq(boardUserPermissions.boardId, input.entityId));
|
||||
if (input.permissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
await transaction.insert(schema.boardUserPermissions).values(
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await db.transaction(async (transaction) => {
|
||||
await transaction.delete(schema.boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId));
|
||||
if (input.permissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
await transaction.insert(schema.boardUserPermissions).values(
|
||||
input.permissions.map((permission) => ({
|
||||
userId: permission.principalId,
|
||||
permission: permission.permission,
|
||||
boardId: input.entityId,
|
||||
})),
|
||||
);
|
||||
});
|
||||
},
|
||||
handleSync(db) {
|
||||
db.transaction((transaction) => {
|
||||
transaction.delete(boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId)).run();
|
||||
if (input.permissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
transaction
|
||||
.insert(boardUserPermissions)
|
||||
.values(
|
||||
input.permissions.map((permission) => ({
|
||||
userId: permission.principalId,
|
||||
permission: permission.permission,
|
||||
boardId: input.entityId,
|
||||
})),
|
||||
);
|
||||
});
|
||||
},
|
||||
handleSync(db) {
|
||||
db.transaction((transaction) => {
|
||||
transaction.delete(boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId)).run();
|
||||
if (input.permissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
transaction
|
||||
.insert(boardUserPermissions)
|
||||
.values(
|
||||
input.permissions.map((permission) => ({
|
||||
userId: permission.principalId,
|
||||
permission: permission.permission,
|
||||
boardId: input.entityId,
|
||||
})),
|
||||
)
|
||||
.run();
|
||||
});
|
||||
},
|
||||
});
|
||||
}),
|
||||
saveGroupBoardPermissions: protectedProcedure
|
||||
.input(validation.board.savePermissions)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
||||
)
|
||||
.run();
|
||||
});
|
||||
},
|
||||
});
|
||||
}),
|
||||
saveGroupBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
||||
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await db.transaction(async (transaction) => {
|
||||
await transaction
|
||||
.delete(schema.boardGroupPermissions)
|
||||
.where(eq(boardGroupPermissions.boardId, input.entityId));
|
||||
if (input.permissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
await transaction.insert(schema.boardGroupPermissions).values(
|
||||
await handleTransactionsAsync(ctx.db, {
|
||||
async handleAsync(db, schema) {
|
||||
await db.transaction(async (transaction) => {
|
||||
await transaction
|
||||
.delete(schema.boardGroupPermissions)
|
||||
.where(eq(boardGroupPermissions.boardId, input.entityId));
|
||||
if (input.permissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
await transaction.insert(schema.boardGroupPermissions).values(
|
||||
input.permissions.map((permission) => ({
|
||||
groupId: permission.principalId,
|
||||
permission: permission.permission,
|
||||
boardId: input.entityId,
|
||||
})),
|
||||
);
|
||||
});
|
||||
},
|
||||
handleSync(db) {
|
||||
db.transaction((transaction) => {
|
||||
transaction.delete(boardGroupPermissions).where(eq(boardGroupPermissions.boardId, input.entityId)).run();
|
||||
if (input.permissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
transaction
|
||||
.insert(boardGroupPermissions)
|
||||
.values(
|
||||
input.permissions.map((permission) => ({
|
||||
groupId: permission.principalId,
|
||||
permission: permission.permission,
|
||||
boardId: input.entityId,
|
||||
})),
|
||||
);
|
||||
});
|
||||
},
|
||||
handleSync(db) {
|
||||
db.transaction((transaction) => {
|
||||
transaction.delete(boardGroupPermissions).where(eq(boardGroupPermissions.boardId, input.entityId)).run();
|
||||
if (input.permissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
transaction
|
||||
.insert(boardGroupPermissions)
|
||||
.values(
|
||||
input.permissions.map((permission) => ({
|
||||
groupId: permission.principalId,
|
||||
permission: permission.permission,
|
||||
boardId: input.entityId,
|
||||
})),
|
||||
)
|
||||
.run();
|
||||
});
|
||||
},
|
||||
});
|
||||
}),
|
||||
)
|
||||
.run();
|
||||
});
|
||||
},
|
||||
});
|
||||
}),
|
||||
importOldmarrConfig: permissionRequiredProcedure
|
||||
.requiresPermission("board-create")
|
||||
.input(importJsonFileSchema)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
import { zfd } from "zod-form-data";
|
||||
|
||||
import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server";
|
||||
import { superRefineCertificateFile, validation } from "@homarr/validation";
|
||||
import { certificateValidFileNameSchema, superRefineCertificateFile } from "@homarr/validation/certificates";
|
||||
|
||||
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
|
||||
|
||||
@@ -20,7 +20,7 @@ export const certificateRouter = createTRPCRouter({
|
||||
}),
|
||||
removeCertificate: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(z.object({ fileName: validation.certificates.validFileNameSchema }))
|
||||
.input(z.object({ fileName: certificateValidFileNameSchema }))
|
||||
.mutation(async ({ input }) => {
|
||||
await removeCustomRootCertificateAsync(input.fileName);
|
||||
}),
|
||||
|
||||
@@ -6,7 +6,15 @@ import { and, createId, eq, handleTransactionsAsync, like, not } from "@homarr/d
|
||||
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
||||
import { groupMembers, groupPermissions, groups } from "@homarr/db/schema";
|
||||
import { everyoneGroup } from "@homarr/definitions";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
|
||||
import {
|
||||
groupCreateSchema,
|
||||
groupSavePartialSettingsSchema,
|
||||
groupSavePermissionsSchema,
|
||||
groupSavePositionsSchema,
|
||||
groupUpdateSchema,
|
||||
groupUserSchema,
|
||||
} from "@homarr/validation/group";
|
||||
|
||||
import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, protectedProcedure } from "../trpc";
|
||||
import { throwIfCredentialsDisabled } from "./invite/checks";
|
||||
@@ -39,7 +47,7 @@ export const groupRouter = createTRPCRouter({
|
||||
|
||||
getPaginated: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.common.paginated)
|
||||
.input(paginatedSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const whereQuery = input.search ? like(groups.name, `%${input.search.trim()}%`) : undefined;
|
||||
const groupCount = await ctx.db.$count(groups, whereQuery);
|
||||
@@ -74,7 +82,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
getById: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.common.byId)
|
||||
.input(byIdSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const group = await ctx.db.query.groups.findFirst({
|
||||
where: eq(groups.id, input.id),
|
||||
@@ -169,7 +177,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
createInitialExternalGroup: onboardingProcedure
|
||||
.requiresStep("group")
|
||||
.input(validation.group.create)
|
||||
.input(groupCreateSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await checkSimilarNameAndThrowAsync(ctx.db, input.name);
|
||||
|
||||
@@ -191,7 +199,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
createGroup: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.group.create)
|
||||
.input(groupCreateSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await checkSimilarNameAndThrowAsync(ctx.db, input.name);
|
||||
|
||||
@@ -209,7 +217,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
updateGroup: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.group.update)
|
||||
.input(groupUpdateSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
|
||||
@@ -225,7 +233,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
savePartialSettings: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.group.savePartialSettings)
|
||||
.input(groupSavePartialSettingsSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
||||
|
||||
@@ -239,7 +247,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
savePositions: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.group.savePositions)
|
||||
.input(groupSavePositionsSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const positions = input.positions.map((id, index) => ({ id, position: index + 1 }));
|
||||
|
||||
@@ -262,7 +270,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
savePermissions: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.group.savePermissions)
|
||||
.input(groupSavePermissionsSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||
|
||||
@@ -277,7 +285,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
transferOwnership: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.group.groupUser)
|
||||
.input(groupUserSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
||||
@@ -291,7 +299,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
deleteGroup: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.common.byId)
|
||||
.input(byIdSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
|
||||
@@ -300,7 +308,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
addMember: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.group.groupUser)
|
||||
.input(groupUserSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
||||
@@ -324,7 +332,7 @@ export const groupRouter = createTRPCRouter({
|
||||
}),
|
||||
removeMember: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.input(validation.group.groupUser)
|
||||
.input(groupUserSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { and, like } from "@homarr/db";
|
||||
import { icons } from "@homarr/db/schema";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { iconsFindSchema } from "@homarr/validation/icons";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||
|
||||
export const iconsRouter = createTRPCRouter({
|
||||
findIcons: publicProcedure.input(validation.icons.findIcons).query(async ({ ctx, input }) => {
|
||||
findIcons: publicProcedure.input(iconsFindSchema).query(async ({ ctx, input }) => {
|
||||
return {
|
||||
icons: await ctx.db.query.iconRepositories.findMany({
|
||||
with: {
|
||||
|
||||
@@ -24,7 +24,12 @@ import {
|
||||
integrationSecretKindObject,
|
||||
} from "@homarr/definitions";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { byIdSchema } from "@homarr/validation/common";
|
||||
import {
|
||||
integrationCreateSchema,
|
||||
integrationSavePermissionsSchema,
|
||||
integrationUpdateSchema,
|
||||
} from "@homarr/validation/integration";
|
||||
|
||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
|
||||
@@ -141,7 +146,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
}),
|
||||
byId: protectedProcedure.input(validation.integration.byId).query(async ({ ctx, input }) => {
|
||||
byId: protectedProcedure.input(byIdSchema).query(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||
const integration = await ctx.db.query.integrations.findFirst({
|
||||
where: eq(integrations.id, input.id),
|
||||
@@ -178,7 +183,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
}),
|
||||
create: permissionRequiredProcedure
|
||||
.requiresPermission("integration-create")
|
||||
.input(validation.integration.create)
|
||||
.input(integrationCreateSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await testConnectionAsync({
|
||||
id: "new",
|
||||
@@ -221,7 +226,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
}),
|
||||
update: protectedProcedure.input(validation.integration.update).mutation(async ({ ctx, input }) => {
|
||||
update: protectedProcedure.input(integrationUpdateSchema).mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||
|
||||
const integration = await ctx.db.query.integrations.findFirst({
|
||||
@@ -282,7 +287,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
}
|
||||
}
|
||||
}),
|
||||
delete: protectedProcedure.input(validation.integration.delete).mutation(async ({ ctx, input }) => {
|
||||
delete: protectedProcedure.input(byIdSchema).mutation(async ({ ctx, input }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||
|
||||
const integration = await ctx.db.query.integrations.findFirst({
|
||||
@@ -298,7 +303,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
|
||||
await ctx.db.delete(integrations).where(eq(integrations.id, input.id));
|
||||
}),
|
||||
getIntegrationPermissions: protectedProcedure.input(validation.board.permissions).query(async ({ input, ctx }) => {
|
||||
getIntegrationPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||
|
||||
const dbGroupPermissions = await ctx.db.query.groupPermissions.findMany({
|
||||
@@ -370,7 +375,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
};
|
||||
}),
|
||||
saveUserIntegrationPermissions: protectedProcedure
|
||||
.input(validation.integration.savePermissions)
|
||||
.input(integrationSavePermissionsSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full");
|
||||
|
||||
@@ -416,7 +421,7 @@ export const integrationRouter = createTRPCRouter({
|
||||
});
|
||||
}),
|
||||
saveGroupIntegrationPermissions: protectedProcedure
|
||||
.input(validation.integration.savePermissions)
|
||||
.input(integrationSavePermissionsSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full");
|
||||
|
||||
|
||||
@@ -1,16 +1,42 @@
|
||||
import type { z } from "zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||
|
||||
const citySchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
country: z.string().optional(),
|
||||
country_code: z.string().optional(),
|
||||
latitude: z.number(),
|
||||
longitude: z.number(),
|
||||
population: z.number().optional(),
|
||||
});
|
||||
|
||||
export const locationSearchCityInput = z.object({
|
||||
query: z.string(),
|
||||
});
|
||||
|
||||
export const locationSearchCityOutput = z
|
||||
.object({
|
||||
results: z.array(citySchema),
|
||||
})
|
||||
.or(
|
||||
z
|
||||
.object({
|
||||
generationtime_ms: z.number(),
|
||||
})
|
||||
.refine((data) => Object.keys(data).length === 1, { message: "Invalid response" })
|
||||
.transform(() => ({ results: [] })), // We fallback to empty array if no results
|
||||
);
|
||||
|
||||
export const locationRouter = createTRPCRouter({
|
||||
searchCity: publicProcedure
|
||||
.input(validation.location.searchCity.input)
|
||||
.output(validation.location.searchCity.output)
|
||||
.input(locationSearchCityInput)
|
||||
.output(locationSearchCityOutput)
|
||||
.query(async ({ input }) => {
|
||||
const res = await fetchWithTimeout(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`);
|
||||
return (await res.json()) as z.infer<typeof validation.location.searchCity.output>;
|
||||
return (await res.json()) as z.infer<typeof locationSearchCityOutput>;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -5,14 +5,15 @@ import type { InferInsertModel } from "@homarr/db";
|
||||
import { and, createId, desc, eq, like } from "@homarr/db";
|
||||
import { iconRepositories, icons, medias } from "@homarr/db/schema";
|
||||
import { createLocalImageUrl, LOCAL_ICON_REPOSITORY_SLUG, mapMediaToIcon } from "@homarr/icons/local";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
|
||||
import { mediaUploadSchema } from "@homarr/validation/media";
|
||||
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../../trpc";
|
||||
|
||||
export const mediaRouter = createTRPCRouter({
|
||||
getPaginated: protectedProcedure
|
||||
.input(
|
||||
validation.common.paginated.and(
|
||||
paginatedSchema.and(
|
||||
z.object({ includeFromAllUsers: z.boolean().default(false), search: z.string().trim().default("") }),
|
||||
),
|
||||
)
|
||||
@@ -51,7 +52,7 @@ export const mediaRouter = createTRPCRouter({
|
||||
}),
|
||||
uploadMedia: permissionRequiredProcedure
|
||||
.requiresPermission("media-upload")
|
||||
.input(validation.media.uploadMedia)
|
||||
.input(mediaUploadSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const content = Buffer.from(await input.file.arrayBuffer());
|
||||
const id = createId();
|
||||
@@ -82,7 +83,7 @@ export const mediaRouter = createTRPCRouter({
|
||||
|
||||
return id;
|
||||
}),
|
||||
deleteMedia: protectedProcedure.input(validation.common.byId).mutation(async ({ ctx, input }) => {
|
||||
deleteMedia: protectedProcedure.input(byIdSchema).mutation(async ({ ctx, input }) => {
|
||||
const dbMedia = await ctx.db.query.medias.findFirst({
|
||||
where: eq(medias.id, input.id),
|
||||
columns: {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { onboarding } from "@homarr/db/schema";
|
||||
import { onboardingSteps } from "@homarr/definitions";
|
||||
import { zodEnumFromArray } from "@homarr/validation";
|
||||
import { zodEnumFromArray } from "@homarr/validation/enums";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
import { getOnboardingOrFallbackAsync, nextOnboardingStepAsync } from "./onboard-queries";
|
||||
|
||||
@@ -5,13 +5,15 @@ import { asc, createId, eq, like } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { searchEngines, users } from "@homarr/db/schema";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { byIdSchema, paginatedSchema, searchSchema } from "@homarr/validation/common";
|
||||
import { searchEngineEditSchema, searchEngineManageSchema } from "@homarr/validation/search-engine";
|
||||
import { mediaRequestOptionsSchema, mediaRequestRequestSchema } from "@homarr/validation/widgets/media-request";
|
||||
|
||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
|
||||
|
||||
export const searchEngineRouter = createTRPCRouter({
|
||||
getPaginated: protectedProcedure.input(validation.common.paginated).query(async ({ input, ctx }) => {
|
||||
getPaginated: protectedProcedure.input(paginatedSchema).query(async ({ input, ctx }) => {
|
||||
const whereQuery = input.search ? like(searchEngines.name, `%${input.search.trim()}%`) : undefined;
|
||||
const searchEngineCount = await ctx.db.$count(searchEngines, whereQuery);
|
||||
|
||||
@@ -41,7 +43,7 @@ export const searchEngineRouter = createTRPCRouter({
|
||||
.then((engines) => engines.map((engine) => ({ value: engine.id, label: engine.name })));
|
||||
}),
|
||||
|
||||
byId: protectedProcedure.input(validation.common.byId).query(async ({ ctx, input }) => {
|
||||
byId: protectedProcedure.input(byIdSchema).query(async ({ ctx, input }) => {
|
||||
const searchEngine = await ctx.db.query.searchEngines.findFirst({
|
||||
where: eq(searchEngines.id, input.id),
|
||||
});
|
||||
@@ -115,7 +117,7 @@ export const searchEngineRouter = createTRPCRouter({
|
||||
|
||||
return null;
|
||||
}),
|
||||
search: protectedProcedure.input(validation.common.search).query(async ({ ctx, input }) => {
|
||||
search: protectedProcedure.input(searchSchema).query(async ({ ctx, input }) => {
|
||||
return await ctx.db.query.searchEngines.findMany({
|
||||
where: like(searchEngines.short, `${input.query.toLowerCase().trim()}%`),
|
||||
with: {
|
||||
@@ -132,21 +134,21 @@ export const searchEngineRouter = createTRPCRouter({
|
||||
}),
|
||||
getMediaRequestOptions: protectedProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("query", "jellyseerr", "overseerr"))
|
||||
.input(validation.common.mediaRequestOptions)
|
||||
.input(mediaRequestOptionsSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const integration = await createIntegrationAsync(ctx.integration);
|
||||
return await integration.getSeriesInformationAsync(input.mediaType, input.mediaId);
|
||||
}),
|
||||
requestMedia: protectedProcedure
|
||||
.unstable_concat(createOneIntegrationMiddleware("interact", "jellyseerr", "overseerr"))
|
||||
.input(validation.common.requestMedia)
|
||||
.input(mediaRequestRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const integration = await createIntegrationAsync(ctx.integration);
|
||||
return await integration.requestMediaAsync(input.mediaType, input.mediaId, input.seasons);
|
||||
}),
|
||||
create: permissionRequiredProcedure
|
||||
.requiresPermission("search-engine-create")
|
||||
.input(validation.searchEngine.manage)
|
||||
.input(searchEngineManageSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await ctx.db.insert(searchEngines).values({
|
||||
id: createId(),
|
||||
@@ -161,7 +163,7 @@ export const searchEngineRouter = createTRPCRouter({
|
||||
}),
|
||||
update: permissionRequiredProcedure
|
||||
.requiresPermission("search-engine-modify-all")
|
||||
.input(validation.searchEngine.edit)
|
||||
.input(searchEngineEditSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const searchEngine = await ctx.db.query.searchEngines.findFirst({
|
||||
where: eq(searchEngines.id, input.id),
|
||||
@@ -188,7 +190,7 @@ export const searchEngineRouter = createTRPCRouter({
|
||||
}),
|
||||
delete: permissionRequiredProcedure
|
||||
.requiresPermission("search-engine-full-all")
|
||||
.input(validation.common.byId)
|
||||
.input(byIdSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await ctx.db
|
||||
.update(users)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { z } from "zod";
|
||||
import { getServerSettingByKeyAsync, getServerSettingsAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import type { ServerSettings } from "@homarr/server-settings";
|
||||
import { defaultServerSettingsKeys } from "@homarr/server-settings";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { settingsInitSchema } from "@homarr/validation/settings";
|
||||
|
||||
import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, publicProcedure } from "../trpc";
|
||||
import { nextOnboardingStepAsync } from "./onboard/onboard-queries";
|
||||
@@ -32,7 +32,7 @@ export const serverSettingsRouter = createTRPCRouter({
|
||||
}),
|
||||
initSettings: onboardingProcedure
|
||||
.requiresStep("settings")
|
||||
.input(validation.settings.init)
|
||||
.input(settingsInitSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await updateServerSettingByKeyAsync(ctx.db, "analytics", input.analytics);
|
||||
await updateServerSettingByKeyAsync(ctx.db, "crawlingAndIndexing", input.crawlingAndIndexing);
|
||||
|
||||
@@ -10,7 +10,20 @@ import { selectUserSchema } from "@homarr/db/validationSchemas";
|
||||
import { credentialsAdminGroup } from "@homarr/definitions";
|
||||
import type { SupportedAuthProvider } from "@homarr/definitions";
|
||||
import { logger } from "@homarr/log";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { byIdSchema } from "@homarr/validation/common";
|
||||
import type { userBaseCreateSchema } from "@homarr/validation/user";
|
||||
import {
|
||||
userChangeColorSchemeSchema,
|
||||
userChangeHomeBoardsSchema,
|
||||
userChangePasswordApiSchema,
|
||||
userChangeSearchPreferencesSchema,
|
||||
userCreateSchema,
|
||||
userEditProfileSchema,
|
||||
userFirstDayOfWeekSchema,
|
||||
userInitSchema,
|
||||
userPingIconsEnabledSchema,
|
||||
userRegistrationApiSchema,
|
||||
} from "@homarr/validation/user";
|
||||
|
||||
import { convertIntersectionToZodObject } from "../schema-merger";
|
||||
import {
|
||||
@@ -28,7 +41,7 @@ import { changeSearchPreferencesAsync, changeSearchPreferencesInputSchema } from
|
||||
export const userRouter = createTRPCRouter({
|
||||
initUser: onboardingProcedure
|
||||
.requiresStep("user")
|
||||
.input(validation.user.init)
|
||||
.input(userInitSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
throwIfCredentialsDisabled();
|
||||
|
||||
@@ -52,7 +65,7 @@ export const userRouter = createTRPCRouter({
|
||||
await nextOnboardingStepAsync(ctx.db, undefined);
|
||||
}),
|
||||
register: publicProcedure
|
||||
.input(validation.user.registrationApi)
|
||||
.input(userRegistrationApiSchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
throwIfCredentialsDisabled();
|
||||
@@ -82,7 +95,7 @@ export const userRouter = createTRPCRouter({
|
||||
create: permissionRequiredProcedure
|
||||
.requiresPermission("admin")
|
||||
.meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } })
|
||||
.input(validation.user.create)
|
||||
.input(userCreateSchema)
|
||||
.output(z.void())
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
throwIfCredentialsDisabled();
|
||||
@@ -259,7 +272,7 @@ export const userRouter = createTRPCRouter({
|
||||
return user;
|
||||
}),
|
||||
editProfile: protectedProcedure
|
||||
.input(validation.user.editProfile)
|
||||
.input(userEditProfileSchema)
|
||||
.output(z.void())
|
||||
.meta({ openapi: { method: "PUT", path: "/api/users/profile", tags: ["users"], protect: true } })
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -318,7 +331,7 @@ export const userRouter = createTRPCRouter({
|
||||
await ctx.db.delete(users).where(eq(users.id, input.userId));
|
||||
}),
|
||||
changePassword: protectedProcedure
|
||||
.input(validation.user.changePasswordApi)
|
||||
.input(userChangePasswordApiSchema)
|
||||
.output(z.void())
|
||||
.meta({ openapi: { method: "PATCH", path: "/api/users/{userId}/changePassword", tags: ["users"], protect: true } })
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
@@ -384,7 +397,7 @@ export const userRouter = createTRPCRouter({
|
||||
.where(eq(users.id, input.userId));
|
||||
}),
|
||||
changeHomeBoards: protectedProcedure
|
||||
.input(convertIntersectionToZodObject(validation.user.changeHomeBoards.and(z.object({ userId: z.string() }))))
|
||||
.input(convertIntersectionToZodObject(userChangeHomeBoardsSchema.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 }) => {
|
||||
@@ -430,7 +443,7 @@ export const userRouter = createTRPCRouter({
|
||||
changeDefaultSearchEngine: protectedProcedure
|
||||
.input(
|
||||
convertIntersectionToZodObject(
|
||||
validation.user.changeSearchPreferences.omit({ openInNewTab: true }).and(z.object({ userId: z.string() })),
|
||||
userChangeSearchPreferencesSchema.omit({ openInNewTab: true }).and(z.object({ userId: z.string() })),
|
||||
),
|
||||
)
|
||||
.output(z.void())
|
||||
@@ -457,7 +470,7 @@ export const userRouter = createTRPCRouter({
|
||||
await changeSearchPreferencesAsync(ctx.db, ctx.session, input);
|
||||
}),
|
||||
changeColorScheme: protectedProcedure
|
||||
.input(validation.user.changeColorScheme)
|
||||
.input(userChangeColorSchemeSchema)
|
||||
.output(z.void())
|
||||
.meta({ openapi: { method: "PATCH", path: "/api/users/changeScheme", tags: ["users"], protect: true } })
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -469,7 +482,7 @@ export const userRouter = createTRPCRouter({
|
||||
.where(eq(users.id, ctx.session.user.id));
|
||||
}),
|
||||
changePingIconsEnabled: protectedProcedure
|
||||
.input(validation.user.pingIconsEnabled.and(validation.common.byId))
|
||||
.input(userPingIconsEnabledSchema.and(byIdSchema))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
// Only admins can change other users ping icons enabled
|
||||
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) {
|
||||
@@ -487,7 +500,7 @@ export const userRouter = createTRPCRouter({
|
||||
.where(eq(users.id, ctx.session.user.id));
|
||||
}),
|
||||
changeFirstDayOfWeek: protectedProcedure
|
||||
.input(convertIntersectionToZodObject(validation.user.firstDayOfWeek.and(validation.common.byId)))
|
||||
.input(convertIntersectionToZodObject(userFirstDayOfWeekSchema.and(byIdSchema)))
|
||||
.output(z.void())
|
||||
.meta({ openapi: { method: "PATCH", path: "/api/users/firstDayOfWeek", tags: ["users"], protect: true } })
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
@@ -522,7 +535,7 @@ export const userRouter = createTRPCRouter({
|
||||
}),
|
||||
});
|
||||
|
||||
const createUserAsync = async (db: Database, input: Omit<z.infer<typeof validation.user.baseCreate>, "groupIds">) => {
|
||||
const createUserAsync = async (db: Database, input: Omit<z.infer<typeof userBaseCreateSchema>, "groupIds">) => {
|
||||
const salt = await createSaltAsync();
|
||||
const hashedPassword = await hashPasswordAsync(input.password, salt);
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import type { Modify } from "@homarr/common/types";
|
||||
import { eq } from "@homarr/db";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { users } from "@homarr/db/schema";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { userChangeSearchPreferencesSchema } from "@homarr/validation/user";
|
||||
|
||||
export const changeSearchPreferencesInputSchema = validation.user.changeSearchPreferences.and(
|
||||
export const changeSearchPreferencesInputSchema = userChangeSearchPreferencesSchema.and(
|
||||
z.object({ userId: z.string() }),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
import { mediaTranscodingRequestHandler } from "@homarr/request-handler/media-transcoding";
|
||||
import { validation } from "@homarr/validation";
|
||||
import { paginatedSchema } from "@homarr/validation/common";
|
||||
|
||||
import type { IntegrationAction } from "../../middlewares/integration";
|
||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||
@@ -12,7 +12,7 @@ const createIndexerManagerIntegrationMiddleware = (action: IntegrationAction) =>
|
||||
export const mediaTranscodingRouter = createTRPCRouter({
|
||||
getDataAsync: publicProcedure
|
||||
.unstable_concat(createIndexerManagerIntegrationMiddleware("query"))
|
||||
.input(validation.common.paginated.pick({ page: true, pageSize: true }))
|
||||
.input(paginatedSchema.pick({ page: true, pageSize: true }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const innerHandler = mediaTranscodingRequestHandler.handler(ctx.integration, {
|
||||
pageOffset: input.page,
|
||||
|
||||
@@ -1,15 +1,39 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
const atLocationInput = z.object({
|
||||
longitude: z.number(),
|
||||
latitude: z.number(),
|
||||
});
|
||||
|
||||
const atLocationOutput = z.object({
|
||||
current_weather: z.object({
|
||||
weathercode: z.number(),
|
||||
temperature: z.number(),
|
||||
windspeed: z.number(),
|
||||
}),
|
||||
daily: z.object({
|
||||
time: z.array(z.string()),
|
||||
weathercode: z.array(z.number()),
|
||||
temperature_2m_max: z.array(z.number()),
|
||||
temperature_2m_min: z.array(z.number()),
|
||||
sunrise: z.array(z.string()),
|
||||
sunset: z.array(z.string()),
|
||||
wind_speed_10m_max: z.array(z.number()),
|
||||
wind_gusts_10m_max: z.array(z.number()),
|
||||
}),
|
||||
});
|
||||
|
||||
export const weatherRouter = createTRPCRouter({
|
||||
atLocation: publicProcedure.input(validation.widget.weather.atLocationInput).query(async ({ input }) => {
|
||||
atLocation: publicProcedure.input(atLocationInput).query(async ({ input }) => {
|
||||
const res = await fetchWithTimeout(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max¤t_weather=true&timezone=auto`,
|
||||
);
|
||||
const json: unknown = await res.json();
|
||||
const weather = await validation.widget.weather.atLocationOutput.parseAsync(json);
|
||||
const weather = await atLocationOutput.parseAsync(json);
|
||||
return {
|
||||
current: weather.current_weather,
|
||||
daily: weather.daily.time.map((value, index) => {
|
||||
|
||||
Reference in New Issue
Block a user