* feat: sanitize input for checklists * feat: add basic editor * feat: use plain text editor for checklist items * feat: remove key from user response
118 lines
2.8 KiB
TypeScript
118 lines
2.8 KiB
TypeScript
import { TRPCError } from "@trpc/server";
|
|
import { z } from "zod";
|
|
|
|
import * as userRepo from "@kan/db/repository/user.repo";
|
|
|
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
|
import { generateAvatarUrl } from "@kan/shared/utils";
|
|
|
|
export const userRouter = createTRPCRouter({
|
|
getUser: protectedProcedure
|
|
.meta({
|
|
openapi: {
|
|
method: "GET",
|
|
path: "/users/me",
|
|
summary: "Get user",
|
|
description:
|
|
"Retrieves the currently authenticated user's profile information",
|
|
tags: ["Users"],
|
|
protect: true,
|
|
},
|
|
})
|
|
.input(z.void())
|
|
.output(
|
|
z.object({
|
|
id: z.string(),
|
|
email: z.string(),
|
|
name: z.string().nullable(),
|
|
image: z.string().nullable(),
|
|
stripeCustomerId: z.string().nullable(),
|
|
apiKey: z
|
|
.object({
|
|
id: z.number(),
|
|
prefix: z.string().nullable(),
|
|
})
|
|
.nullable(),
|
|
}),
|
|
)
|
|
.query(async ({ ctx }) => {
|
|
const userId = ctx.user?.id;
|
|
|
|
if (!userId)
|
|
throw new TRPCError({
|
|
message: `User not authenticated`,
|
|
code: "UNAUTHORIZED",
|
|
});
|
|
|
|
const result = await userRepo.getById(ctx.db, userId);
|
|
|
|
if (!result) {
|
|
throw new TRPCError({
|
|
message: `User not found`,
|
|
code: "NOT_FOUND",
|
|
});
|
|
}
|
|
|
|
const apiKey = result.apiKeys[0];
|
|
|
|
// Generate presigned URL for avatar
|
|
const imageUrl = await generateAvatarUrl(result.image);
|
|
|
|
return {
|
|
...result,
|
|
image: imageUrl,
|
|
apiKey: apiKey ? { id: apiKey.id, prefix: apiKey.prefix } : null,
|
|
};
|
|
}),
|
|
update: protectedProcedure
|
|
.meta({
|
|
openapi: {
|
|
method: "PUT",
|
|
path: "/users",
|
|
summary: "Update user",
|
|
description:
|
|
"Updates the currently authenticated user's profile information",
|
|
tags: ["Users"],
|
|
protect: true,
|
|
},
|
|
})
|
|
.input(
|
|
z.object({
|
|
name: z.string().optional(),
|
|
image: z.string().optional(),
|
|
}),
|
|
)
|
|
.output(
|
|
z.object({
|
|
name: z.string().nullable(),
|
|
image: z.string().nullable(),
|
|
}),
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
const userId = ctx.user?.id;
|
|
|
|
if (!userId)
|
|
throw new TRPCError({
|
|
message: `User not authenticated`,
|
|
code: "UNAUTHORIZED",
|
|
});
|
|
|
|
const result = await userRepo.update(ctx.db, userId, input);
|
|
|
|
if (!result) {
|
|
throw new TRPCError({
|
|
message: `User not found`,
|
|
code: "NOT_FOUND",
|
|
});
|
|
}
|
|
|
|
// Generate presigned URL for avatar
|
|
const imageUrl = await generateAvatarUrl(result.image);
|
|
|
|
return {
|
|
...result,
|
|
image: imageUrl,
|
|
};
|
|
}),
|
|
});
|