Files
sorvor-back/src/userRoute.ts

189 lines
4.7 KiB
TypeScript

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<typeof opinionInsertSchema>;
type UserInsertSchema = z.infer<typeof userInsertSchema>;
type UserUpdateSchema = z.infer<typeof userUpdateSchema>;
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]);
}