From 22c24c0202287eb874388621147e94ea1627e5fd Mon Sep 17 00:00:00 2001 From: rafiarrafif Date: Sat, 21 Jun 2025 22:02:41 +0700 Subject: [PATCH] add:utils:file:store | create utility for store file in system --- .gitignore | 5 +++- bun.lockb | Bin 81728 -> 82831 bytes package.json | 2 ++ src/constants/file | 0 src/helpers/files/saveFile/index.ts | 26 ++++++++++++++++++ .../files/saveFile/modules/saveAvatar.ts | 17 ++++++++++++ src/modules/user/services/editUser.service.ts | 11 ++++++-- 7 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 src/constants/file create mode 100644 src/helpers/files/saveFile/index.ts create mode 100644 src/helpers/files/saveFile/modules/saveAvatar.ts diff --git a/.gitignore b/.gitignore index 1b5d52f..e8f77c2 100644 --- a/.gitignore +++ b/.gitignore @@ -54,4 +54,7 @@ server.exe # debug and cached routes /src/routes.ts -/src/modules/debug \ No newline at end of file +/src/modules/debug + +# uploaded files +/uploads \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 1066575bd847c1302a60f499b1bb567c428ad859..c7e7dac4e38dab11fdf4a49ca186c708550c02ef 100644 GIT binary patch delta 13732 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 B9x?y` delta 13280 zcmeHO33OD&w!PI!lUyVrVM;fhflMWLkdFq|xKE3NE899@stTS$6kT?F;gvW4WIn0CuDj(#;-L zAQJ#NDSPyIWTk89wITQ^OWkguyihZ%-wLAonu3#-pAI$-U%has(ugGEkkXfhg-y>1Q3qe;)0lv|h~N$DA*3df?~#?W*7-jLj1#)P8$ ztTf3j^);y-Jp#$o^cy4_T!iFdXJuq$*kYok#b&jE{DQoK?D1BqH+R|{S0ZQTGuDx-O2rFb2TXUL$bH?TBuf+AUP;cYSIJAt-J<1 z9@N;h!m${WRMJWve1Rr2A-Q~bYqi~hkUR=I^gPCH536!GIP`Tr1J#VD7nOUr5pAi; z+ZMPS4sz#dQ}V;})5lA8YG(`+9jLdljqs&hV~{&ETy=d+c3yZ!+UU#^;M~m`NbY6{ zBoB9Ogt`^#cakJbtLJxcp1NX`H-T)~Sq-fKaPINKH2rdo&w%9dkB4NtA&?li+tUph4zA9ST(K!6 z8`jbI6`MM+W00)>5Rw~s1Cs5_Ai4emjZcPTyU~zr_b4P!P=Z$8N-GagxVPk~iQU=9 zXQkNri|;M3oxjwZD?|wu`8$M6<#?u1HJ&d~e0_)EoS@?RcF~v0>pR3es>bsm#RoX# zNF1D)Cy#+j>f1yfl|#1+x*pI8*2zY1NwO;}CBP;gp`t*Cd=xs>f^` zrBu|wVf@A@NeMJPFwq!TN0O2$sX?Ngirf&rRpZNGeOYg~UWfKIw97ql0(Vo2D;n9v ze2Q=65bseDp75rTLw4b0?u&9SN@-w|H-RCXJQ9`o+vG37k`-1FV3Qjn82hMYupAE7 zOD_|fDZYtAHX>{vh0e%riN~nCi9`5MHJ&bt4{{g@;oXDoV))09vr|$)q8x;fRQo6i zw8<{8L1@810)1=+OHdfvGoAsn)Aag@BAlvC4tX4c5fS0>QAS=4_QzNa)qb?kY?tE@ zpKRhy?0gQzH+9HcpkqT>S(!l;Z*j;k;_zf=yeXxLO}?ZtFJ%&<5!<{bD8rD7HKvvw z{G+U*W1KGzP+m}PDuMT@R2}Rvlm=2lh~4mZAQeO038Z}?cB2cy`J}Q&I~!0!s9hMS zDAXY$s65mmZ$gBopcMlTSw0Qs08>^+w4|aihdc@~9t#~Dz>KZd80I{{W;oo45}MhK zc?jQ%CXX1+ZA9tR%!H z9|cPSL&U+G26(^N!0J*8%7%b-1@qy0hSg2!b}PH#SW`-9ZI}J<5_tq=^{6D&CI(P> zYlm?ebiU zz{aXz(LN5NeeLXWn`UZ{k`id6!PI@rhsDca9LQ?_r!` z=N{FVkt#aHiHE7UlU@E8I=07zz@knY)gJwX;Sd4i2~j*Vt_15$2O1?BKV&^k$C-H> zxd>QbaYJlIYg@F%@h)v&R>q)qVBDJgICAQ=^O>d ztX)2fZw56iMQfYf3jJ`*BY2wFj3dDk=s?3nc^h(%s(S3oHuw%1styQ|?(Rehad!C# zl!H`dC!3svUY}GeAimdhrrR(%3FSatDLe3W(&G?C zg2Rv=MYj{|#$AXut8zS@LoN}uu#>SzT3Gd4mJ;lC<8?UMjgst%Mq50;3pqC(IUEwmeSn-=Dx+~#mx>%-=qO!}oLb{Y zN6Ikog0a%y|wIDl2VLgdu;%Ky*_%$QoE zA9899LQWmQcgU%ATjNEg+T|jrjRiTSJ;PlG?dz7{#;cWosYv*c;cqp`05kyvfLDo< zSb75%fc3!uCzh;l2C%+4z=H0&CEYK1;CNFh_E$s8{&CEYK1 z*iKE)lILTv#{UJW&Yw$bh$RnTn8sOh$4_XSB`@?yjo&ZXE=|+3WIhUzf$;z*maLz^ z1Ze_(xoVkzlx#RrRsTuy&MwgOEIERvYMdo6WUN$WD zOSV`Cu+L=xr!s);HUjL^W`Gk*wttBU(*2T${+gowv*h}3XnIJx*h_YE8#^`KgCv*l z(#lyf->q?$?CySm^>1nN03;`tJoa~(AU!~;n}7>Azg|Lr zy@b>w;{h)pWeY+7*GmX9@&D%~)Qb;xr1t-++_`4puK|>l?xb%=`_YPYr?660x{Kzf z`%(7{r--4Y87_*;@S_`GHcA=eqVr&z$2dhiT?Jb`#*YS%b&5pVFxExA#`=*l(}_Pp zrDnS57T9hu2Z<~fZOQbbj4Y=}p&ejDv;3&>I4AzjoHouyzT^Dp1F-HCINn8j!3xJa zMNg^#%N*}VttU7|Z_1tEBGUvvItu2b<`Z4?F<9wDr|3tOU{fdhQOqQ#=uah+T-0`w zADsb9rO0d-eG9fC+lfB{SAosVhJQIuF_@O-z`q>$2j-%bT=)mJIoBy3r>kJAbKzf} zQ#?T%^59<{{L6QW;gp&W|G;*GJw>7b{^i5J0w?|)zXNP&0sJd;iqVu-2>%MIr@+4{@DFS}HJ=Lqz)Gh&#YCzEn>rQ#6*)yVl@!6h zBKQZEOOeI!4{SxTQ{+<>*xX|HH_a&uY3VfhHx2%QO`(*h;UC!Mr=6mRu7a(88vadp zifOcAI{cds|7JMFbV{88|G;*GxkTxdP%3TQm7Q#QUt<-!G`~xdpKL?z9`^9!oN^Gk|f=MrDhQaq2+1w6l|l=Uw04Xs%}HXsv+S3ODc6hQhx?iAas zhMfW1%m&Kw_ojL;`HF5kmu?L53V0LWw`}o!TQxmJc z0{9K^9up*;^H=yBMI%W^A=!q1RZZ4(+#>7vXTv^C_l>6G-yr3h?psawy*7ky$0a&( zEG>gyJ6-_)x-$TGV8>&4ANUaX2>2M_50ZmG1@JEL0Z;;XfIJ`{$N_SJNkBF*3K$Kf z)B58U_Y=rH2@D5D08asZf&Rci;4xqTFbGHm9tE60KcE}X9q0ih0EqzqL98LbzqSxf z{I9Zn%|Q9J#lJcI0sc(z1?mX=*!4k%f0KU(;McEo7&roa4pahP0DlMO0nY+F?X!T{ zKq)X2;21RRgMlGHFQ6yT8|VTg0r=x3|NEMh45aXnLHwuu!s1`R4SnErDIB;4TnG5i3nzi^f#X0KumN}pcoldB*arL!*b2N1 zYyp-7O929w04soKAPleoR-gk=#Zz|{nN7fp!0W&c;0<6cuo74WtOgDNi-00vKkzn? z!=pk9K1IimwP^PZvOHNk0d9huTLz2-IN}BX97V}HE5Gv+_?)Ra85Ylji2``#5;gGZ z@hURc!|xH~y823V{NEr+PBL^``<;0G^U(fD)h-{VO|U7RU^MBkyT|E3<(IfQhnuxO^_K z1mNgh0PuP)1bD~I2Uy48Jg@n7;CWy@!11sK;F#cmS`H9<&xRZ%>j0k4=Kvn@Hh?3O zqx1!UqjM9m5hw!!fcn5gzy_cZ;HSyPTK+}ImngETp?foOtYtI+xaJmsTjmzIHEwCE z#@XgoEwA??Yn<&~0|?-7_tK!f67KB>e-qdZu&x}x5dPTdd!U3u!Cpw_2Y|PLew?T3 zswXT5k$VSt8{p;#oWcWX`(24f|InIMH0Os|4fR_@hu$mp#2(r3zCrXN+mG|`v0d?_ z8K1{DezeALDAmJamL*2rY5mM+i3TwuDxz})Hckaf;JD}WF^eV{oBAw3N#}^jh$x(? zH2kzV_6M!z`!9SF6TICgu(td%O6sEKlHWF$?F;dmskCW}=qyRMBzpC<$)evxT)d`d z<~7UTI>8_sox)%UojPq6YpMFQRWzsYGv;9ZcA|gs%AYLL9viAO7Rk0<4K(UZl&D9Q z=ggupy>`YdT2R$3GhO<+D8UMpgE%lp@cX#X550skX44 z+{i2S@Nu^7mep|Ei_V{o!Z(-YoY|sZW_;tdI`ma!kNH|tk(hSsj{&HSaI;Okp~MY? z+Yxmzm7L`PDh&tgR~{QpondKJ_We48n85>r)jc0-cD^ruTunT0w&)iTuLR`0zRx+b zP;pu@J4GALn+?}wdh5K&qTgF=(Ytr2>?4(H6*HyJJ#+=l==U6FJalYCq3MHOwapx- z+zV!Lfs!tmE&46VSIo=5D;^Tp9afzqVj|##ZZ-cxKhctoU9gIcdRRb8Jz3cP3*&8WjgvqisFxz%s}%sEvqJzS!3*wa%N@l6^?+b^0lVQG)u*$~k; zDX3GI>b*B&4T5Jl67D2X)kU*rCPvVXOO5KFHyo$b>5@5CzfRfp#ThHwCB7M<3^p<% z1}7Yv3qXqn4KEf(T^#DKjS?*`Ye+LLnUuE7+S8jpy~N>o=2Ab4e#3HFy?aTa{r7q+ zO)BdXP5pj0Tl5>55BaxPG%V#YZWtSkhcu8%e&!aR!?OqN{<)t;zZ6+|dt`&=#tVH6 zf_EdvAbC;9WwS-UL>ac;@O^ZHUFW=c6|K<`R%`op~eBEh6y z71XbFzR=p!u|sLbr~qM4Y8Jy$dCWv*m!pF9`A)3puzuaqbiO9Sxw!B{G_3T?H$Cavm0gwxo+Pmm zHhgMO#V=;@FrE0tYA6YzfUCQL_4}S-h95?xUcQ`z)~xCjoeZV#ubN`?3!)!<`qS%6$a7tZ?zPw3%Mq>AShUk;*Ugq*V0_a04galeL3-sVtrpJcfh2zAGv4y6 z*`i-fy*oj?p3|b+aacto%y0@8Q2$@eF(qgPFGtV7d0%f29ona^Qm(|-r_I$%lZ#(= zN6(I$c3x>;@mTF&zvOx;ec0gdx*Y6=@;G%yhEpZ_??xAYH8=dn{<~7pjZLEM&iyyW z3t^`=H?6qj`ov8$F1Z%o9Ba7Jkxt$;S@b)pH+?>tJaYG|eDw2H#;OER;4QNVr6aeS zguz+;Qfu~_+2>yjJy=z1xFnoL-ZB~1gj3n?jcM_%5cOzRUlfKD;dJMg34ajbcR^$7 zRBcxBO}w$B>!{ytR^F>N$LN=7f7vs4$r|4?aZ3A9{3_J1)gBE#_-~KxdjCbGBr*a= z(|xDD(u&ohU&Y<%zq-6|;r?W8lxRimOMUW-!)np5=kA~SQ|h-p?m9GEjEkK%YYv5C z3H8hOIXV7KPIj@@)LJSp!(jbF@3c+*`=o6@l&OsnEh&>?QeRv&`}d}5qfxalQB`4r zqWK+bu)V$d<9hJ^^dl4QdB4jCkaAW@xB{GW$0XEuxALU7?li^yR>xg)uzun9joF7w zw)eZ^V-VQCyl15CR;qV5N;SiuG%H}7jETdin#bS0$27Zr(S%y#LD976uF0ZbFOJ{- z$Rh!U1&a+L6IHNFJW2yjX7Aq1w7FHd57STz?W;w6KwQ-jVbm~L@PckJNkl^NqM`?mg~*5H_pUcYCG(QiXnZU_i@zwL$owFdfq>GloU zTE+YyH`JCirMvgcvHJb$QBCg1^}`zdR$H#$z&^LK;MxYqZ~JRYhQzJtD@?)qo$a~a zuVy|Km@u{0U`E^uk1)mRm%J(QQp<4**Uqgq(6514oLRc1VNlPTwI%!FRvZ?l82#S3 zZ=k)yUa|a8t$}{CJhtzlH#aQXRa{%5gz7{3zl$f=NUIz|^jw9T+nt*Z!OM}qi)Y8h z91>IgYjSE3u}E9&tG*Co(yCO0XtD=ykkdj|{y&<6qMVFXf$c@`o*ZxSWW7C(K=EG7 LJ>6qOAOC*`RSKFj diff --git a/package.json b/package.json index 0837bc7..54a28cd 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@prisma/client": "^6.7.0", "@types/bcrypt": "^5.0.2", "@types/jsonwebtoken": "^9.0.9", + "@types/mime-types": "^3.0.1", "aws-sdk": "^2.1692.0", "bcrypt": "^5.1.1", "cookie": "^1.0.2", @@ -20,6 +21,7 @@ "ioredis": "^5.6.1", "joi": "^17.13.3", "jsonwebtoken": "^9.0.2", + "mime-types": "^3.0.1", "mock-aws-s3": "^4.0.2", "nock": "^14.0.4", "ua-parser-js": "^2.0.3" diff --git a/src/constants/file b/src/constants/file new file mode 100644 index 0000000..e69de29 diff --git a/src/helpers/files/saveFile/index.ts b/src/helpers/files/saveFile/index.ts new file mode 100644 index 0000000..9457096 --- /dev/null +++ b/src/helpers/files/saveFile/index.ts @@ -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 => { + 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; +}; diff --git a/src/helpers/files/saveFile/modules/saveAvatar.ts b/src/helpers/files/saveFile/modules/saveAvatar.ts new file mode 100644 index 0000000..b09492a --- /dev/null +++ b/src/helpers/files/saveFile/modules/saveAvatar.ts @@ -0,0 +1,17 @@ +import { saveFile } from ".."; +import { AppError } from "../../../error/instances/app"; + +export const saveAvatar = async (file: File): Promise => { + 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-", + }); +}; diff --git a/src/modules/user/services/editUser.service.ts b/src/modules/user/services/editUser.service.ts index 4ea4b58..e749bfe 100644 --- a/src/modules/user/services/editUser.service.ts +++ b/src/modules/user/services/editUser.service.ts @@ -7,6 +7,7 @@ import { checkUserEmailAndUsernameAvailabillityService } from "./checkUserEmailA import { logoutService } from "../../auth/services/logout.service"; import { loginFromSystemService } from "../../auth/services/loginFromSystem.service"; import { UserHeaderInformation } from "../../../helpers/http/userHeader/getUserHeaderInformation/types"; +import { saveAvatar } from "../../../helpers/files/saveFile/modules/saveAvatar"; export const editUserService = async ( cookie: string, @@ -29,6 +30,11 @@ export const editUserService = async ( "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 const fieldsToUpdate: Partial = { ...(payload.username && payload.username !== jwtSession.user.username @@ -47,9 +53,7 @@ export const editUserService = async ( ...(payload.bioProfile !== undefined ? { bioProfile: payload.bioProfile } : {}), - ...(payload.profilePicture !== undefined - ? { profilePicture: payload.profilePicture } - : {}), + ...(storeAvatar !== undefined ? { profilePicture: storeAvatar } : {}), ...(payload.commentPicture !== undefined ? { 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 await updateUserRepo(jwtSession.user.username, fieldsToUpdate); + // Clear the session and re-login the user to get a new JWT token await logoutService(cookie); const newUserSession = await loginFromSystemService( jwtSession.userId,