import { router, verifiedPhone, publicProcedure, protectedProcedure, } from "./trpc"; import { db } from "./db"; import { phoneToken, user, userOpinion } from "./schema"; import { createInsertSchema } from "drizzle-zod"; import { z } from "zod"; import { SQL, eq } from "drizzle-orm"; import { Config } from "./config"; import { TRPCError } from "@trpc/server"; import * as jwt from "jsonwebtoken"; const userInsertSchema = createInsertSchema(user, { cid: (schema) => schema.cid.length(13).refine(isValidThaiID, { message: "Invalid Thai ID" }), }); const userUpdateSchema = userInsertSchema .omit({ id: true, cid: true, phone: true }) .partial(); const opinionInsertSchema = createInsertSchema(userOpinion) .omit({ userId: true, }) .array() .default([]); const opinionUpdateSchema = createInsertSchema(userOpinion) .omit({ userId: true, }) .required({ opinionId: true }); type OpinionInsertSchema = z.infer; type UserInsertSchema = z.infer; type UserUpdateSchema = z.infer; export const userRoute = router({ getAllUser: publicProcedure .input( z.object({ offset: z.number().default(0), limit: z.number().max(50).default(10), group: z.number().optional(), zone: z.number().optional(), }) ) .query( async ({ input }) => await getAllUser(input.offset, input.limit, input.group, input.zone) ), createUser: publicProcedure .input( userInsertSchema.omit({ id: true }).extend({ opinions: opinionInsertSchema, }) ) .mutation( async ({ input }) => await createUser({ ...input }, input.opinions) ), updateUser: protectedProcedure .input(userUpdateSchema) .mutation(async ({ input, ctx }) => await updateUser(ctx.user.id, input)), login: publicProcedure .input(z.object({ cid: z.string(), phone: z.string() })) .mutation(async ({ input }) => await login(input.cid, input.phone)), changeOpinion: protectedProcedure .input(opinionUpdateSchema) .mutation( async ({ input, ctx }) => await changeOpinion(input.opinionId, ctx.user.id, input.choice) ), }); async function getAllUser( offset: number, limit: number, group?: number, zone?: number ) { let users = await db.query.user.findMany({ with: { group: true, opinions: true, zone: { with: { province: true }, }, }, limit, offset, where: (user, { eq, and }) => { let conditions: SQL[] = []; if (group) { conditions.push(eq(user.group, group)); } if (zone) { conditions.push(eq(user.zone, zone)); } return and(...conditions); }, }); return users; } async function createUser( newUser: UserInsertSchema, opinions: OpinionInsertSchema ) { try { let result = ( await db.insert(user).values(newUser).returning({ id: user.id }) )[0]; for (let op of opinions) { await db.insert(userOpinion).values({ ...op, userId: result.id }); } return { token: createJWT(newUser.phone) }; } catch (e) { console.error(e); throw new Error(`Unable to create new user`); } } async function updateUser(userId: number, update: UserUpdateSchema) { try { await db.update(user).set(update).where(eq(user.id, userId)); return { status: "success" }; } catch (e) { console.error(e); throw new Error(`Unable to update user`); } } async function login(cid: string, phone: string) { let user = await db.query.user.findFirst({ where: (user, { and, eq }) => and(eq(user.cid, cid), eq(user.phone, phone)), }); if (user === undefined) { throw new TRPCError({ message: "Invalid Credentials", code: "BAD_REQUEST", }); } else { return { token: createJWT(user.phone) }; } } function createJWT(phone: string) { return jwt.sign({ phone: phone }, Config.jwt_secret, { expiresIn: "365d", }); } async function changeOpinion( opinionId: number, userId: number, opinion: OpinionInsertSchema[0]["choice"] ) { try { db.insert(userOpinion) .values({ opinionId, userId, choice: opinion, }) .onConflictDoUpdate({ target: [userOpinion.userId, userOpinion.opinionId], set: { choice: opinion }, }); } catch (e) { console.error(e); throw new TRPCError({ message: "Error updating schema", code: "INTERNAL_SERVER_ERROR", }); } } function isValidThaiID(id: string) { if (!/^\d{13}$/.test(id)) { return false; } let sum = 0; for (let i = 0; i < 12; i++) { sum += Number(id[i]) * (13 - i); } const checkSum = (11 - (sum % 11)) % 10; return checkSum === Number(id[12]); }