added update image route

This commit is contained in:
2024-04-20 14:52:43 +07:00
parent ed8c0e77f2
commit ee9963c3b6
11 changed files with 914 additions and 35 deletions

View File

@@ -1,12 +1,13 @@
import { router, publicProcedure, protectedProcedure } from "./trpc";
import { db } from "./db";
import { opinion, user, userOpinion } from "./schema";
import { imageToUser, opinion, user, userOpinion } from "./schema";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod";
import { SQL, eq } from "drizzle-orm";
import { SQL, count, eq } from "drizzle-orm";
import { Config } from "./config";
import { TRPCError } from "@trpc/server";
import * as jwt from "jsonwebtoken";
import { createClient, createUploadImageUrl } from "./minio";
const userInsertSchema = createInsertSchema(user, {
cid: (schema) =>
@@ -33,6 +34,41 @@ type UserInsertSchema = z.infer<typeof userInsertSchema>;
type UserUpdateSchema = z.infer<typeof userUpdateSchema>;
export const userRoute = router({
createUser: publicProcedure
.input(
userInsertSchema.omit({ id: true }).extend({
opinions: opinionInsertSchema,
})
)
.mutation(
async ({ input }) => await createUser({ ...input }, input.opinions)
),
// changeImage: protectedProcedure
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)
),
requestChangeImage: protectedProcedure
.input(z.object({ imageName: z.string(), contentType: z.string() }))
.mutation(
async ({ input, ctx }) =>
await requestChangeImage(
ctx.user.id,
input.imageName,
input.contentType
)
),
confirmChangeImage: protectedProcedure.mutation(
async ({ ctx }) => await confirmChangeImage(ctx.user.id, ctx.user.image)
),
getAllUser: publicProcedure
.input(
z.object({
@@ -53,27 +89,6 @@ export const userRoute = router({
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(
@@ -112,6 +127,7 @@ async function getAllUser(
phone: hidePhone(u.phone),
}));
}
async function createUser(
newUser: UserInsertSchema,
opinions: OpinionInsertSchema
@@ -154,12 +170,6 @@ async function login(cid: string, phone: string) {
}
}
function createJWT(phone: string) {
return jwt.sign({ phone: phone }, Config.jwt_secret, {
expiresIn: "365d",
});
}
async function changeOpinion(
opinionId: number,
userId: number,
@@ -203,6 +213,99 @@ async function changeOpinion(
});
}
}
async function requestChangeImage(
userId: number,
imageName: string,
contentType: string
) {
const mc = createClient();
// Check if the image is valid
const allowedImageTypes = z.enum(["image/png", "image/jpeg", "image/webp"]);
if (!allowedImageTypes.safeParse(contentType).success) {
throw new TRPCError({
message: "Only PNG, JPEG, and WEBP images are allowed",
code: "BAD_REQUEST",
});
}
const allowedExtension = z.enum(["png", "jpeg", "jpg", "webp"]);
const extension = imageName.split(".").pop();
if (!allowedExtension.safeParse(extension).success) {
throw new TRPCError({
message: "only .png, .jpeg, .jpg, and .webp extensions are allowed",
code: "BAD_REQUEST",
});
}
// Create a unique image name
let tryCount = 0;
let objectName: string | null = null;
while (tryCount < 3) {
let imageName = `${generateRandomString()}.${extension}`;
let ok = await db
.select({ value: count(user.image) })
.from(user)
.where(eq(user.image, imageName))
.then((v) => v[0].value === 0);
if (ok) {
objectName = imageName;
break;
}
}
if (objectName === null) {
throw new TRPCError({
message: "Unable to create image request (conflicting name)",
code: "INTERNAL_SERVER_ERROR",
});
}
// Store a record in the database
await db
.insert(imageToUser)
.values({ userId, imageName: objectName })
.onConflictDoUpdate({
target: [imageToUser.userId],
set: { imageName: objectName },
});
return await createUploadImageUrl(mc, objectName, contentType);
}
async function confirmChangeImage(userId: number, oldImage: string | null) {
const mc = createClient();
let rs = await db
.select({ imageName: imageToUser.imageName })
.from(imageToUser)
.where(eq(imageToUser.userId, userId));
if (rs.length === 0) {
throw new TRPCError({
message: "No image request found",
code: "BAD_REQUEST",
});
}
let imageName = rs[0].imageName;
const isImageExist = await mc
.statObject(Config.bucketName, imageName)
.then(() => true)
.catch(() => false);
if (!isImageExist) {
throw new TRPCError({
message: "Image not found",
code: "BAD_REQUEST",
});
}
const promises: Promise<any>[] = [];
if (oldImage) {
promises.push(mc.removeObject(Config.bucketName, oldImage).catch(() => {}));
}
const updateUser = db
.update(user)
.set({ image: imageName })
.where(eq(user.id, userId));
const deleteRecord = db
.delete(imageToUser)
.where(eq(imageToUser.userId, userId));
await Promise.all([...promises, updateUser, deleteRecord]);
return { status: "success" };
}
function isValidThaiID(id: string) {
if (!/^\d{13}$/.test(id)) {
return false;
@@ -219,3 +322,13 @@ function hidePhone(phone: string | null) {
if (phone === null) return phone;
return phone.slice(0, 2).concat("******").concat(phone.slice(-3));
}
function createJWT(phone: string) {
return jwt.sign({ phone: phone }, Config.jwt_secret, {
expiresIn: "365d",
});
}
function generateRandomString() {
return Math.random().toString(36).substring(2, 15);
}