Skip to content

Commit 1478756

Browse files
committed
feat: #1243 add api routes
1 parent 982ab43 commit 1478756

File tree

17 files changed

+433
-2457
lines changed

17 files changed

+433
-2457
lines changed

apps/nextjs/src/app/[locale]/manage/tools/api/components/swagger-ui.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import type { OpenAPIV3 } from "openapi-types";
3+
import type { OpenAPIObject } from "openapi3-ts/oas31";
44
import SwaggerUI from "swagger-ui-react";
55

66
// 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";
99
import "../swagger-ui.css";
1010

1111
interface SwaggerUIClientProps {
12-
document: OpenAPIV3.Document;
12+
document: OpenAPIObject;
1313
}
1414

1515
export const SwaggerUIClient = ({ document }: SwaggerUIClientProps) => {

apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_delete-user-button.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ export const DeleteUserButton = ({ user }: DeleteUserButtonProps) => {
3131
children: t("user.action.delete.confirm", { username: user.name }),
3232
// eslint-disable-next-line no-restricted-syntax
3333
async onConfirm() {
34-
await mutateUserDeletionAsync(user.id);
34+
await mutateUserDeletionAsync({
35+
userId: user.id,
36+
});
3537
},
3638
}),
3739
[user, mutateUserDeletionAsync, openConfirmModal, t],

apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { Button, Group, Radio, Stack } from "@mantine/core";
4+
import type { DayOfWeek } from "@mantine/dates";
45
import dayjs from "dayjs";
56
import localeData from "dayjs/plugin/localeData";
67

@@ -43,7 +44,7 @@ export const FirstDayOfWeek = ({ user }: FirstDayOfWeekProps) => {
4344
});
4445
const form = useZodForm(validation.user.firstDayOfWeek, {
4546
initialValues: {
46-
firstDayOfWeek: user.firstDayOfWeek,
47+
firstDayOfWeek: user.firstDayOfWeek as DayOfWeek,
4748
},
4849
});
4950

apps/nextjs/src/app/api/[...trpc]/route.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { headers } from "next/headers";
22
import { userAgent } from "next/server";
33
import type { NextRequest } from "next/server";
4-
import { createOpenApiFetchHandler } from "trpc-swagger/build/index.mjs";
4+
import { createOpenApiFetchHandler } from "trpc-to-openapi";
55

66
import { appRouter, createTRPCContext } from "@homarr/api";
77
import { hashPasswordAsync } from "@homarr/auth";

package.json

-5
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,5 @@
4545
"packageManager": "[email protected]",
4646
"engines": {
4747
"node": ">=22.11.0"
48-
},
49-
"pnpm": {
50-
"patchedDependencies": {
51-
52-
}
5348
}
5449
}

packages/api/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"next": "^14.2.18",
4444
"react": "^18.3.1",
4545
"superjson": "2.2.1",
46-
"trpc-swagger": "^1.2.6"
46+
"trpc-to-openapi": "^2.0.2"
4747
},
4848
"devDependencies": {
4949
"@homarr/eslint-config": "workspace:^0.2.0",

packages/api/src/open-api.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { generateOpenApiDocument } from "trpc-swagger";
1+
import { generateOpenApiDocument } from "trpc-to-openapi";
22

33
import { appRouter } from "./root";
44

packages/api/src/router/app.ts

+9-43
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,17 @@ import { TRPCError } from "@trpc/server";
22

33
import { asc, createId, eq, inArray, like } from "@homarr/db";
44
import { apps } from "@homarr/db/schema/sqlite";
5+
import { selectAppSchema } from "@homarr/db/validationSchemas";
56
import { validation, z } from "@homarr/validation";
67

8+
import { convertIntersectionToZodObject } from "../schema-merger";
79
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
810
import { canUserSeeAppAsync } from "./app/app-access-control";
911

1012
export const appRouter = createTRPCRouter({
1113
all: protectedProcedure
1214
.input(z.void())
13-
.output(
14-
z.array(
15-
z.object({
16-
name: z.string(),
17-
id: z.string(),
18-
description: z.string().nullable(),
19-
iconUrl: z.string(),
20-
href: z.string().nullable(),
21-
}),
22-
),
23-
)
15+
.output(z.array(selectAppSchema))
2416
.meta({ openapi: { method: "GET", path: "/api/apps", tags: ["apps"], protect: true } })
2517
.query(({ ctx }) => {
2618
return ctx.db.query.apps.findMany({
@@ -29,17 +21,7 @@ export const appRouter = createTRPCRouter({
2921
}),
3022
search: protectedProcedure
3123
.input(z.object({ query: z.string(), limit: z.number().min(1).max(100).default(10) }))
32-
.output(
33-
z.array(
34-
z.object({
35-
name: z.string(),
36-
id: z.string(),
37-
description: z.string().nullable(),
38-
iconUrl: z.string(),
39-
href: z.string().nullable(),
40-
}),
41-
),
42-
)
24+
.output(z.array(selectAppSchema))
4325
.meta({ openapi: { method: "GET", path: "/api/apps/search", tags: ["apps"], protect: true } })
4426
.query(({ ctx, input }) => {
4527
return ctx.db.query.apps.findMany({
@@ -50,17 +32,7 @@ export const appRouter = createTRPCRouter({
5032
}),
5133
selectable: protectedProcedure
5234
.input(z.void())
53-
.output(
54-
z.array(
55-
z.object({
56-
name: z.string(),
57-
id: z.string(),
58-
iconUrl: z.string(),
59-
description: z.string().nullable(),
60-
href: z.string().nullable(),
61-
}),
62-
),
63-
)
35+
.output(z.array(selectAppSchema.pick({ id: true, name: true, iconUrl: true, href: true, description: true })))
6436
.meta({
6537
openapi: {
6638
method: "GET",
@@ -83,15 +55,7 @@ export const appRouter = createTRPCRouter({
8355
}),
8456
byId: publicProcedure
8557
.input(validation.common.byId)
86-
.output(
87-
z.object({
88-
name: z.string(),
89-
id: z.string(),
90-
description: z.string().nullable(),
91-
iconUrl: z.string(),
92-
href: z.string().nullable(),
93-
}),
94-
)
58+
.output(selectAppSchema)
9559
.meta({ openapi: { method: "GET", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
9660
.query(async ({ ctx, input }) => {
9761
const app = await ctx.db.query.apps.findFirst({
@@ -136,7 +100,9 @@ export const appRouter = createTRPCRouter({
136100
}),
137101
update: permissionRequiredProcedure
138102
.requiresPermission("app-modify-all")
139-
.input(validation.app.edit)
103+
.input(convertIntersectionToZodObject(validation.app.edit))
104+
.output(z.void())
105+
.meta({ openapi: { method: "PATCH", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
140106
.mutation(async ({ ctx, input }) => {
141107
const app = await ctx.db.query.apps.findFirst({
142108
where: eq(apps.id, input.id),

packages/api/src/router/invite.ts

+32-15
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,51 @@ import { TRPCError } from "@trpc/server";
33

44
import { asc, createId, eq } from "@homarr/db";
55
import { invites } from "@homarr/db/schema/sqlite";
6+
import { selectInviteSchema } from "@homarr/db/validationSchemas";
67
import { z } from "@homarr/validation";
78

89
import { createTRPCRouter, protectedProcedure } from "../trpc";
910
import { throwIfCredentialsDisabled } from "./invite/checks";
1011

1112
export const inviteRouter = createTRPCRouter({
12-
getAll: protectedProcedure.query(async ({ ctx }) => {
13-
throwIfCredentialsDisabled();
14-
const dbInvites = await ctx.db.query.invites.findMany({
15-
orderBy: asc(invites.expirationDate),
16-
columns: {
17-
token: false,
18-
},
19-
with: {
20-
creator: {
21-
columns: {
13+
getAll: protectedProcedure
14+
.output(
15+
z.array(
16+
selectInviteSchema
17+
.pick({
2218
id: true,
23-
name: true,
19+
expirationDate: true,
20+
})
21+
.extend({ creator: z.object({ name: z.string().nullable(), id: z.string() }) }),
22+
),
23+
)
24+
.input(z.undefined())
25+
.meta({ openapi: { method: "GET", path: "/api/invites", tags: ["invites"], protect: true } })
26+
.query(async ({ ctx }) => {
27+
throwIfCredentialsDisabled();
28+
return await ctx.db.query.invites.findMany({
29+
orderBy: asc(invites.expirationDate),
30+
columns: {
31+
token: false,
32+
},
33+
with: {
34+
creator: {
35+
columns: {
36+
id: true,
37+
name: true,
38+
},
2439
},
2540
},
26-
},
27-
});
28-
return dbInvites;
29-
}),
41+
});
42+
}),
3043
createInvite: protectedProcedure
3144
.input(
3245
z.object({
3346
expirationDate: z.date(),
3447
}),
3548
)
49+
.output(z.object({ id: z.string(), token: z.string() }))
50+
.meta({ openapi: { method: "POST", path: "/api/invites", tags: ["invites"], protect: true } })
3651
.mutation(async ({ ctx, input }) => {
3752
throwIfCredentialsDisabled();
3853
const id = createId();
@@ -56,6 +71,8 @@ export const inviteRouter = createTRPCRouter({
5671
id: z.string(),
5772
}),
5873
)
74+
.output(z.undefined())
75+
.meta({ openapi: { method: "DELETE", path: "/api/invites/{id}", tags: ["invites"], protect: true } })
5976
.mutation(async ({ ctx, input }) => {
6077
throwIfCredentialsDisabled();
6178
const dbInvite = await ctx.db.query.invites.findFirst({

packages/api/src/router/test/user.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ describe("delete should delete user", () => {
316316

317317
await db.insert(schema.users).values(initialUsers);
318318

319-
await caller.delete(defaultOwnerId);
319+
await caller.delete({ userId: defaultOwnerId });
320320

321321
const usersInDb = await db.select().from(schema.users);
322322
expect(usersInDb).toHaveLength(2);

0 commit comments

Comments
 (0)