From 88b803044e027b98d46de125d5e76cc11cf189b0 Mon Sep 17 00:00:00 2001 From: TrupleCPU Date: Sat, 18 Oct 2025 22:41:58 +0800 Subject: [PATCH] feat: implemented file-upload activity --- flask_file_upload_mvc/.gitignore | 4 ++ .../__pycache__/app.cpython-314.pyc | Bin 0 -> 1298 bytes .../__pycache__/config.cpython-314.pyc | Bin 0 -> 1038 bytes .../__pycache__/extensions.cpython-314.pyc | Bin 0 -> 246 bytes flask_file_upload_mvc/app.py | 27 ++++++++ flask_file_upload_mvc/config.py | 14 ++++ .../file_controller.cpython-314.pyc | Bin 0 -> 4059 bytes .../controllers/file_controller.py | 59 ++++++++++++++++ flask_file_upload_mvc/extensions.py | 3 + flask_file_upload_mvc/logs/app.log | 21 ++++++ .../__pycache__/uploaded_file.cpython-314.pyc | Bin 0 -> 837 bytes flask_file_upload_mvc/models/uploaded_file.py | 6 ++ flask_file_upload_mvc/requirements.txt | Bin 0 -> 522 bytes .../__pycache__/file_service.cpython-314.pyc | Bin 0 -> 4025 bytes .../services/file_service.py | 65 ++++++++++++++++++ 15 files changed, 199 insertions(+) create mode 100644 flask_file_upload_mvc/.gitignore create mode 100644 flask_file_upload_mvc/__pycache__/app.cpython-314.pyc create mode 100644 flask_file_upload_mvc/__pycache__/config.cpython-314.pyc create mode 100644 flask_file_upload_mvc/__pycache__/extensions.cpython-314.pyc create mode 100644 flask_file_upload_mvc/app.py create mode 100644 flask_file_upload_mvc/config.py create mode 100644 flask_file_upload_mvc/controllers/__pycache__/file_controller.cpython-314.pyc create mode 100644 flask_file_upload_mvc/controllers/file_controller.py create mode 100644 flask_file_upload_mvc/extensions.py create mode 100644 flask_file_upload_mvc/logs/app.log create mode 100644 flask_file_upload_mvc/models/__pycache__/uploaded_file.cpython-314.pyc create mode 100644 flask_file_upload_mvc/models/uploaded_file.py create mode 100644 flask_file_upload_mvc/requirements.txt create mode 100644 flask_file_upload_mvc/services/__pycache__/file_service.cpython-314.pyc create mode 100644 flask_file_upload_mvc/services/file_service.py diff --git a/flask_file_upload_mvc/.gitignore b/flask_file_upload_mvc/.gitignore new file mode 100644 index 0000000..1e1d699 --- /dev/null +++ b/flask_file_upload_mvc/.gitignore @@ -0,0 +1,4 @@ +.env/ +.venv/ +env/ +.env \ No newline at end of file diff --git a/flask_file_upload_mvc/__pycache__/app.cpython-314.pyc b/flask_file_upload_mvc/__pycache__/app.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebc209e7352a42c7ee11d5cce421938ffa410510 GIT binary patch literal 1298 zcma)6-*3}a6h8K~W5-V0u!9?C;p#$EJ3TE~*6D)83dWgtRB_b>evsd)T#-bIy0Z^PO{#qnsNZ z(SRNI+R>fcBETO!h>kLJj^LLjhMEQ$pbpbW=QQ7XuR9Qsl*QBVRU z16A}PRMosNPzsXOWH}Yd$F_Vt_9L&VL{&^hW-7@j4)iLNPei=fa?XFL8}i(#m}Mjk zWuD!KAEqpn|CJk{0RK-Z1%`S_WC~MJU2Kh{q0OT~9BxD}jup~DnsrQ;&&0gQtEQvd zg^cM4k8OVFUKX&>6}t#Sgs=*|JQFh`FU}hRfAas#Q&o-BCtm_I*xsl3M7gLYwro!$ z^TYR+VQ>CS_{qXKh+4JS7N@`xGjN)1l0T*Mm(gjkgxu(#TWmKL4zA z-LBsu&I1-D7Tk8zYEiAUwv3n7N{cs)FBq(n`<5SIcYRa6Xm&amY`3*JGd*ki^}upS z&d+`|J!_KZ}pwL~=i%THPaNK(J}s6nWkDk8s55fL>krv~tU5x|Nmdx3jCo z8>MV{p~8Ed?{;k0Y*Yr93%d>w>}=F4eDR$JRK4Q3jjm0uc^@$~{!8$GVPCJ2AP6rY z`2t1{Ve$}W4&mghguYXF>~5hxpq{p^9l*q|FtNL_2OqqW$9BGAKi@3bGT?5Ag+kL8}7cs_EeuN`|j_hhcG=iexj zAiha~@G)yKl)twJnU}KK!`t{kp4gWs`ck5|y1n}C+FJyP$=51Ksh!O|bh0lgz2)uY l-LqTEze|&UA&^eJO)>_n`rGG!M?Z}mTSW5DNM9UJz5|CE9M}K= literal 0 HcmV?d00001 diff --git a/flask_file_upload_mvc/__pycache__/config.cpython-314.pyc b/flask_file_upload_mvc/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..866b994aa151a5bf2a51569b10320ef636fdc97c GIT binary patch literal 1038 zcmZ`&O>7cD6rNd@zXjT2`jb{|DJCYQ(b}d_X&UVoXuB){Cm;FoT6h3|mY5(71`4G@$7(N|7_>m1>xz!oX4D6%Lsqgoct;`m@Xe`c-(Gpl0HLwK;M1XMH7CprlA2-VE4FLLK+yZ0k z;$#Q+W$`^Mb6=LFht)rqwTETvo6G*6H7^hg5{R`g*MXe>+GRs7Vn+i+K!e1AhFI&| z)Dlq2h2rkxp;A@bmuL2>#=1B=ORd2boJxccg^Sd|a9m6z?#ED&!ezs^9!n$*5RaiG zh3$Z-7zwRXS1KNki9x&?3kO*OYh_O#j7E7`+0~No*sPq?hH*Jt%419gj7!Q+Mb@zs zsbdS|Gn$Drakb1dWlaE(N8K#!@2U1O4htYyIZ&D}NmfG{W jqk5RB{}FA1DRwM`-+=u)nEVMGhps=o4Z2%^DI@kT?1k>M literal 0 HcmV?d00001 diff --git a/flask_file_upload_mvc/__pycache__/extensions.cpython-314.pyc b/flask_file_upload_mvc/__pycache__/extensions.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f5ad73d4a0af9627e6186604508169fabc6f527 GIT binary patch literal 246 zcmdPq8HtjOCT*Lu{b-vxG*OXB3Q%>RCkLhC21wYXONa# zF3wglp~b01#W7xvkqW+^u09F@&M|43IjOp(1v&YNDKQWuKqB!Fk@(!Q2A!y@JYH95%W6DWy57c14^(Lm7d%m>)=dU}j`wyvv~ekd?nVrirPD9Vi9>&-Xq+ literal 0 HcmV?d00001 diff --git a/flask_file_upload_mvc/app.py b/flask_file_upload_mvc/app.py new file mode 100644 index 0000000..3b9982a --- /dev/null +++ b/flask_file_upload_mvc/app.py @@ -0,0 +1,27 @@ +import os +import logging +from flask import Flask +from extensions import db +from config import Config + +app = Flask(__name__) +app.config.from_object(Config) + +db.init_app(app) + +os.makedirs(app.config['LOG_FOLDER'], exist_ok=True) +logging.basicConfig( + filename=f"{app.config['LOG_FOLDER']}/app.log", + level=logging.INFO, + format='%(asctime)s [%(levelname)s] %(message)s' +) + +from controllers.file_controller import file_bp + +app.register_blueprint(file_bp) + +with app.app_context(): + db.create_all() + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/flask_file_upload_mvc/config.py b/flask_file_upload_mvc/config.py new file mode 100644 index 0000000..b0e8885 --- /dev/null +++ b/flask_file_upload_mvc/config.py @@ -0,0 +1,14 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +class Config: + SQLALCHEMY_DATABASE_URI = ( + f"mysql+pymysql://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}@" + f"{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}" + ) + SQLALCHEMY_TRACK_MODIFICATIONS = False + UPLOAD_FOLDER = os.getenv('UPLOAD_FOLDER', 'uploads') + LOG_FOLDER = 'logs' + DEBUG = True diff --git a/flask_file_upload_mvc/controllers/__pycache__/file_controller.cpython-314.pyc b/flask_file_upload_mvc/controllers/__pycache__/file_controller.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d198305d2cbdce7a84a7db70ea529d0585f9c09 GIT binary patch literal 4059 zcmc&%TWk~A89p=P%Q)j?NF0)dU~n1;ambA(3M8b21Pl&k!F4WMiov+?#PMQcb7mZc zNUNr(Qq#UH4}Hk0NE@jYWz{}Z>Pvc&mas3hPZOCa)oiNmg_nH`%|5JlU;6)NW*nOm zwo)tgNdD){IsduLIp6nxzmHW_RuCxj54Wc`MMD08FLrTSmF*smkOgvsM7a^7;gcWX z!o0?Z1x*M$G)LH}Im0f^bqM=}5qDVB#IQ&6ge$ZP*5(+I!d}hG>fA`BR(YLoBvI$} zsvh24LzAz|kw$yrooCb)b$4)4@r30U_0*Fdt6QtyWlTj>DjVZ18B??Cn98y-RV8D5 zyN;2|#_TB>Qyb-)NU-`--0S6_)9?&u@=A`TXEei2l{39FZ%dW8mk$t!UHPq&m5C!i2!J69InPe`nX+K%yj8?2lLnUu=L%ANVEr5ORRT zY={6fqI@TZK*16fy7{KBm?cWu_M6^lb0p@n-y*o9mrB%8>g@&a#29eVE#iI28D1NU zFo{(Cd#{&#UC42sNtz2vhJ!&3z@Vy$lrq2PayEc10TV4s^3(fpk4EKqVSP3PMlh$h!pC^2G?YEW-6UB z9Qiy9cB1VuglT1t;=R+o=;L7!#(51-Xl_!;Y3b~Y;m%}Jsq{?BaI!@VLDgu`K>>;| z>r^(ARHz}^A7OBciW>+(F~-mK$HrBKs3-hLvkMfvsC?{{(b*@{%2D2 zrf>h!h<>zBZ|E=hPOXgUzB9V_%yYT!-FJTU&eFuXe0b5ZS$k-swt20#dHH+mwI_7n z$rbJohd*rnUF+}LHu^8F^st%*u}7o2+_f%teYxd?5nrfH@KN`ghFi$PVGit*7U^t<@T9xzY*6^9 ziG%t_K_}RGUnd?$=Kw^yes*H%!$vbR zL2P#9xC!p4)sZse5(15tC`6_bZtyHF;VrQ;!9|Ku#BRgr6?NEx!x&tQFTmi#jKO@Y z7*!$&zn2P%+ZbQYU|^&x#}D#viso~Nk+G89#(efv-!8JhWdCm5e^8_tx#p|R!$}8b zvRYs&o1aOhe*$NTQNA*v#uJ*Bn4G>1N-{59P_%%}N{X>8SV1Lt7*5b3de;zAie~Z? zuZ12!gCT|Nn{)qTwc>F6V$ zF16{7HnkBpbFX?WplDrG9m7qkV9#eekio-X1B`d~4Bd>BH&AuBYye@YS{O zRlWW@1^JqO{iZJ8)Ww_s16dgABM*maz^?X5!-K*TN7rz_@T7;sdcPB_;W`((6dDVa zi9nH7e_|gEA^`L<%T92I)W?D^1m?0}UIZr|Fe^oHa<5M&0i668S00{ASBYVCWM!(n2OXhz4UXLNG>~A z?qI!{i6LVmo{&*{I}4wzcG#(&$sxQnCRv6UkIy7-EAcqR=w>)+Hm{la8Pg?-Y@>KK zACPUZ#1gLom^PnLPSaD+hEP<00|qpO<31+RCnU5^LSWZO=r5%GV^a0E3g7*arMW^y z%Yyr_ocKfk_x+1^mwT667W@V7*bCmv9p4rR-?oLdt%HR3po=aaew2TdLZ99jdVzA literal 0 HcmV?d00001 diff --git a/flask_file_upload_mvc/controllers/file_controller.py b/flask_file_upload_mvc/controllers/file_controller.py new file mode 100644 index 0000000..d818984 --- /dev/null +++ b/flask_file_upload_mvc/controllers/file_controller.py @@ -0,0 +1,59 @@ +import uuid +from flask import Blueprint, request, jsonify, send_file, current_app +from services.file_service import save_file, update_file, delete_file +from models.uploaded_file import UploadedFile +import logging + +file_bp = Blueprint('file_bp', __name__) + +@file_bp.route('/upload', methods=['POST']) +def upload_file(): + try: + if 'file' not in request.files: + return jsonify({'message': 'No file uploaded'}), 400 + + file = request.files['file'] + folder = f"{current_app.config['UPLOAD_FOLDER']}/{uuid.uuid4().hex}" # new folder per request + uploaded = save_file(file, folder) + return jsonify({'message': 'Upload successful', 'id': uploaded.id, 'path': uploaded.file_path}), 201 + + except Exception as e: + logging.error(f"Upload error: {e}") + return jsonify({'message': str(e)}), 400 + + +@file_bp.route('/file/', methods=['GET']) +def get_file(file_id): + try: + file = UploadedFile.query.get(file_id) + if not file: + return jsonify({'message': 'File not found'}), 404 + + # You can test using Postman → GET http://localhost:5000/file/ + return send_file(file.file_path, as_attachment=True) + except Exception as e: + logging.error(f"Get error: {e}") + return jsonify({'message': str(e)}), 400 + +@file_bp.route('/file/', methods=['PUT']) +def update_existing_file(file_id): + try: + if 'file' not in request.files: + return jsonify({'message': 'No file provided'}), 400 + file = request.files['file'] + updated = update_file(file_id, file, current_app.config['UPLOAD_FOLDER']) + return jsonify({'message': 'File updated successfully', 'path': updated.file_path}), 200 + + except Exception as e: + logging.error(f"Update error: {e}") + return jsonify({'message': str(e)}), 400 + +@file_bp.route('/file/', methods=['DELETE']) +def delete_existing_file(file_id): + try: + delete_file(file_id) + return jsonify({'message': 'File deleted successfully'}), 200 + + except Exception as e: + logging.error(f"Delete error: {e}") + return jsonify({'message': str(e)}), 400 \ No newline at end of file diff --git a/flask_file_upload_mvc/extensions.py b/flask_file_upload_mvc/extensions.py new file mode 100644 index 0000000..2e1eeb6 --- /dev/null +++ b/flask_file_upload_mvc/extensions.py @@ -0,0 +1,3 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() \ No newline at end of file diff --git a/flask_file_upload_mvc/logs/app.log b/flask_file_upload_mvc/logs/app.log new file mode 100644 index 0000000..1497dd4 --- /dev/null +++ b/flask_file_upload_mvc/logs/app.log @@ -0,0 +1,21 @@ +2025-10-18 22:38:56,221 [INFO] WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. + * Running on http://127.0.0.1:5000 +2025-10-18 22:38:56,222 [INFO] Press CTRL+C to quit +2025-10-18 22:38:56,223 [INFO] * Restarting with stat +2025-10-18 22:38:56,879 [WARNING] * Debugger is active! +2025-10-18 22:38:56,881 [INFO] * Debugger PIN: 681-091-574 +2025-10-18 22:39:11,250 [INFO] File saved: uploads/b18fc54fb8274d10a6f05da3d689dfbf\ccdf1d981d1a481ebe1e10010c00f68f_va.png +2025-10-18 22:39:11,255 [INFO] 127.0.0.1 - - [18/Oct/2025 22:39:11] "POST /upload HTTP/1.1" 201 - +2025-10-18 22:39:17,622 [INFO] 127.0.0.1 - - [18/Oct/2025 22:39:17] "GET /upload/1 HTTP/1.1" 404 - +2025-10-18 22:39:38,104 [INFO] 127.0.0.1 - - [18/Oct/2025 22:39:38] "GET /file/1 HTTP/1.1" 200 - +2025-10-18 22:40:31,093 [INFO] File updated: ID=1 +2025-10-18 22:40:31,096 [INFO] 127.0.0.1 - - [18/Oct/2025 22:40:31] "PUT /file/1 HTTP/1.1" 200 - +2025-10-18 22:40:46,270 [INFO] 127.0.0.1 - - [18/Oct/2025 22:40:46] "GET /file/1 HTTP/1.1" 200 - +2025-10-18 22:40:52,996 [INFO] 127.0.0.1 - - [18/Oct/2025 22:40:52] "GET /file/2 HTTP/1.1" 404 - +2025-10-18 22:40:57,066 [ERROR] Update error: File not found +2025-10-18 22:40:57,067 [INFO] 127.0.0.1 - - [18/Oct/2025 22:40:57] "PUT /file/2 HTTP/1.1" 400 - +2025-10-18 22:41:00,979 [ERROR] Delete error: File not found +2025-10-18 22:41:00,980 [INFO] 127.0.0.1 - - [18/Oct/2025 22:41:00] "DELETE /file/2 HTTP/1.1" 400 - +2025-10-18 22:41:03,949 [INFO] File deleted from storage: uploads/b18fc54fb8274d10a6f05da3d689dfbf\f022d7b6212947b7b05f6c5603bbbcb1_731-7312556_download-png.png +2025-10-18 22:41:03,953 [INFO] Record deleted: ID=1 +2025-10-18 22:41:03,953 [INFO] 127.0.0.1 - - [18/Oct/2025 22:41:03] "DELETE /file/1 HTTP/1.1" 200 - diff --git a/flask_file_upload_mvc/models/__pycache__/uploaded_file.cpython-314.pyc b/flask_file_upload_mvc/models/__pycache__/uploaded_file.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e9cdf203e15d9a091e28dbaad6ca9a24ecbeefe GIT binary patch literal 837 zcmb_aL2DC16rR~lnrsqHEC$U*TgAg77SV_?6j6hr5gWp455gwHCOc`DZZ>geR?IDk zc<80pOG|rbk9zBm$sr7QP*2{3h*y1+ZCXX}+ z7a2@f^azX>0Sr(B8MuO$Fvk~B7R@6=Ifo3jj%$%=!O59dL(EkpavAB-uQJrJQ0!*w zBnzSA7N3C~pgQ8%KwN34T&+iH6KlVxBWMeJ>Q7Ea1~!!2O33rrKWa07*UqEH&pZx! z0}LPQX#YtkVMKY-n1MmWL~5nyb}gIQWyhsYVI;|(?{qA`Pu6L_3~}5GT-R#4^aHBu zDG?{cvpSRzkszegwF6h;6d}(8%RRbD6Vi5k#$CswUY8JGPG8KFx^B?%M6BX*x=MYa zIJTI1!hOeE74bH-m*ql}i1aMJCbEPuZgHnYEDmYSfKvu)aZnq}@rY`vXs$5oGxO2? zXLHMyhmYrKB~uDq3ntGp+pfjd!3{`;4C%aRnc$nc%nTW7Pwp3beP4e5$tDf6GHh?q zNK0oK*e-+O4`P literal 0 HcmV?d00001 diff --git a/flask_file_upload_mvc/models/uploaded_file.py b/flask_file_upload_mvc/models/uploaded_file.py new file mode 100644 index 0000000..73ac5be --- /dev/null +++ b/flask_file_upload_mvc/models/uploaded_file.py @@ -0,0 +1,6 @@ +from extensions import db + +class UploadedFile(db.Model): + id = db.Column(db.Integer, primary_key=True) + filename = db.Column(db.String(255), nullable=False) + file_path = db.Column(db.String(255), nullable=False) \ No newline at end of file diff --git a/flask_file_upload_mvc/requirements.txt b/flask_file_upload_mvc/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..a2fbb496ea1102651203c1551504362dea0cffe3 GIT binary patch literal 522 zcmZ{hO-sXI5QOI}_){uPtfB`GUOb2e#e+wQF{ZUP3HcE9$E(l0Zxcn3u!QW)&dzMU zJ|Fa=wJL2?sMQ%yq63{$bM0$SdBAgUV<+?ve_=If&0MA}WyTzIK^=$b3-Cu(LQ#wLss5?UzsBIxD)b%a z^Qu!87qe81EhST8I=(7Gm;IwmN{S1ja0~k2*&y6sqsU2Vwp_eyVohd;I8cYTeKTQEN+?sy#<>SFPY)a0FiE$NWas zOyx`}si>xuv?fy3bz^RBAu@e4HvRG4>G=->3-cesOe;H`jQ04{g%7SpKaxc%M{nQw zz4uOFdd?gCAc~VNWVN$Lmr`+gMZ1)k-VO2d_Obp(8z$_I;6x`_e`G2|*hw>{Xx%k~r`U`}8}u|} zCWTE+T8~~mz}7Tcb{}BT8P*#dWKyFY;6kP_fx9s@b_<&zB+MIb3|X)8Fe7@?lH#p- z<=r131jO~Eti+@hm5=!i6knp+fgT^r#Fga@yS7k{KM;j$UVpdd!uq7na6FY2rzw@F zYLH~rx*A^*g(Q_JI;2Xn*`%PFu%1$l%i_99P@zgblagtbphk+JOVRmMi!91=QcA1F zxFD$JMQL>vV%L(A5{YCwp^{{JN%EUjf`+NK`*9h-B&7tA>bBUj>E!2GF@~U0J5aY~ zy=qO1Yg*{Z=*&K!7Yar1t4%_66oUaHQ~nE7Ij(B!+ar&=9(7f0zGpUH)zM!#F^ z6s!~b#Jn~8_3XASFFYqBRp;qF$_sO55757llJyhuN<>ODR z`Hg~WGB;a2=GwLFSa$6@_PtL_$1djrFNk$3uo?JcvP}B-ohS33mYugi32bnC^Y+&K z=KS`RGC5Uyt`K*TxXYw(pV+qMzMk8DFW<5Ehr;oZG8uhg>&`7ujHAV7O4h;fvknFr z*GoGr1nU4`vp8ThbE1qfY-|CBh0S{d7xTS=R?Et{`WqS$`5&)@& z_BL1m9380(LZ`95ops$UrorRU8Z)FxW6$B$U=e8nWF0qQKG=VF1v~fK0Mi7>(}%Ta zfHY}9Dut8aYjI#=@*AaDx}+zluZkFDY^;855vZBZ77=&;tFCcaPxQmJ`Fyq z38+CIs>XzH0Q~RbD-!Ps5pm;&Y^sw(8Bp)aoA1jlKO;qIJ23R2=;7dFB1$rS}V*PECrMFO#57+bB zS{haN|NoPU{I8xomaEyF@^%68Vg8G%uZ2CcN?S`ee5rG)tgMc%R+ zgQul5(c*3P8(L`*LRt`t4B9xUaX}DMB5=XGM5R@)tVlGT5U+VRxD}p2q`*()tl6y$Se&R^a4s=tT5giAW+S^Mhys%alwBgPPJ()pxTZyrqn(b)%s*T zsxQO9JMfdghYF(Fby|BM|HI+s#hw#BgNj4ie>bfWt2tr zNX2!o=sH)n4p*(tZ|&dM3w@JM->qDlDPEc>Oa=<>+hyx4W;?BR~F1+H-zT_M~yRQ~F*K5M_AM>xxPQK^$pquac L*LWvCsp