add:utils:file:store | create utility for store file in system

This commit is contained in:
rafiarrafif
2025-06-21 22:02:41 +07:00
parent 87c3719203
commit 22c24c0202
7 changed files with 57 additions and 4 deletions

3
.gitignore vendored
View File

@ -55,3 +55,6 @@ server.exe
# debug and cached routes # debug and cached routes
/src/routes.ts /src/routes.ts
/src/modules/debug /src/modules/debug
# uploaded files
/uploads

BIN
bun.lockb

Binary file not shown.

View File

@ -13,6 +13,7 @@
"@prisma/client": "^6.7.0", "@prisma/client": "^6.7.0",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/jsonwebtoken": "^9.0.9", "@types/jsonwebtoken": "^9.0.9",
"@types/mime-types": "^3.0.1",
"aws-sdk": "^2.1692.0", "aws-sdk": "^2.1692.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"cookie": "^1.0.2", "cookie": "^1.0.2",
@ -20,6 +21,7 @@
"ioredis": "^5.6.1", "ioredis": "^5.6.1",
"joi": "^17.13.3", "joi": "^17.13.3",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"mime-types": "^3.0.1",
"mock-aws-s3": "^4.0.2", "mock-aws-s3": "^4.0.2",
"nock": "^14.0.4", "nock": "^14.0.4",
"ua-parser-js": "^2.0.3" "ua-parser-js": "^2.0.3"

0
src/constants/file Normal file
View File

View File

@ -0,0 +1,26 @@
import { mkdir, writeFile } from "fs/promises";
import path from "path";
import crypto from "crypto";
import mime from "mime-types";
interface SaveFileOptions {
folder: string;
prefix?: string;
}
export const saveFile = async (
file: File,
{ folder, prefix }: SaveFileOptions
): Promise<string> => {
const ext = mime.extension(file.type) || "bin";
const uniqueName = `${prefix ?? ""}${crypto.randomUUID()}.${ext}`;
const relativeFolder = path.join("uploads", folder);
const relativePath = path.join(relativeFolder, uniqueName);
const absolutePath = path.join(process.cwd(), relativePath);
await mkdir(path.dirname(absolutePath), { recursive: true });
await writeFile(absolutePath, Buffer.from(await file.arrayBuffer()));
return relativePath;
};

View File

@ -0,0 +1,17 @@
import { saveFile } from "..";
import { AppError } from "../../../error/instances/app";
export const saveAvatar = async (file: File): Promise<string> => {
const allowedTypes = ["image/png", "image/jpeg", "image/webp"];
if (!allowedTypes.includes(file.type)) {
throw new AppError(
415,
"Unsupported Media Type. File must be in .jpg, .png, or .webp format"
);
}
return await saveFile(file, {
folder: "avatar",
prefix: "usr-",
});
};

View File

@ -7,6 +7,7 @@ import { checkUserEmailAndUsernameAvailabillityService } from "./checkUserEmailA
import { logoutService } from "../../auth/services/logout.service"; import { logoutService } from "../../auth/services/logout.service";
import { loginFromSystemService } from "../../auth/services/loginFromSystem.service"; import { loginFromSystemService } from "../../auth/services/loginFromSystem.service";
import { UserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation/types"; import { UserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation/types";
import { saveAvatar } from "../../../helpers/files/saveFile/modules/saveAvatar";
export const editUserService = async ( export const editUserService = async (
cookie: string, cookie: string,
@ -29,6 +30,11 @@ export const editUserService = async (
"The username or email has already taken by another user." "The username or email has already taken by another user."
); );
// Store the avatar to the file system if provided in the payload
let storeAvatar: string | undefined = undefined;
if (payload.profilePicture)
storeAvatar = await saveAvatar(payload.profilePicture as File);
// Prepare the fields to update, only include fields that are provided in the payload // Prepare the fields to update, only include fields that are provided in the payload
const fieldsToUpdate: Partial<Prisma.UserUpdateInput> = { const fieldsToUpdate: Partial<Prisma.UserUpdateInput> = {
...(payload.username && payload.username !== jwtSession.user.username ...(payload.username && payload.username !== jwtSession.user.username
@ -47,9 +53,7 @@ export const editUserService = async (
...(payload.bioProfile !== undefined ...(payload.bioProfile !== undefined
? { bioProfile: payload.bioProfile } ? { bioProfile: payload.bioProfile }
: {}), : {}),
...(payload.profilePicture !== undefined ...(storeAvatar !== undefined ? { profilePicture: storeAvatar } : {}),
? { profilePicture: payload.profilePicture }
: {}),
...(payload.commentPicture !== undefined ...(payload.commentPicture !== undefined
? { commentPicture: payload.commentPicture } ? { commentPicture: payload.commentPicture }
: {}), : {}),
@ -61,6 +65,7 @@ export const editUserService = async (
// Update the user in the database, use username from the JWT session to find the user // Update the user in the database, use username from the JWT session to find the user
await updateUserRepo(jwtSession.user.username, fieldsToUpdate); await updateUserRepo(jwtSession.user.username, fieldsToUpdate);
// Clear the session and re-login the user to get a new JWT token
await logoutService(cookie); await logoutService(cookie);
const newUserSession = await loginFromSystemService( const newUserSession = await loginFromSystemService(
jwtSession.userId, jwtSession.userId,