Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: #1243 add api routes #1286

Merged
merged 1 commit into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -43,7 +44,7 @@ export const FirstDayOfWeek = ({ user }: FirstDayOfWeekProps) => {
});
const form = useZodForm(validation.user.firstDayOfWeek, {
initialValues: {
firstDayOfWeek: user.firstDayOfWeek,
firstDayOfWeek: user.firstDayOfWeek as DayOfWeek,
},
});

Expand Down
2 changes: 1 addition & 1 deletion apps/nextjs/src/app/api/[...trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
5 changes: 0 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/open-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { generateOpenApiDocument } from "trpc-swagger";
import { generateOpenApiDocument } from "trpc-to-openapi";

import { appRouter } from "./root";

Expand Down
52 changes: 9 additions & 43 deletions packages/api/src/router/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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({
Expand All @@ -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",
Expand All @@ -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({
Expand Down Expand Up @@ -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),
Expand Down
47 changes: 32 additions & 15 deletions packages/api/src/router/invite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/router/test/user.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading