From ef2267af6949269da20f11d8df6bec8c649c5bc1 Mon Sep 17 00:00:00 2001 From: zaapptt Date: Tue, 18 Nov 2025 17:48:02 +0800 Subject: [PATCH] jwt-authentication --- app.py | 37 ++++++ config.py | 14 +++ .../auth_controller.cpython-313.pyc | Bin 0 -> 1433 bytes .../file_controller.cpython-313.pyc | Bin 0 -> 3952 bytes controllers/auth_controller.py | 26 +++++ controllers/file_controller.py | 53 +++++++++ extensions.py | 3 + logs/app.log | 110 ++++++++++++++++++ .../__pycache__/uploaded_file.cpython-313.pyc | Bin 0 -> 806 bytes models/uploaded_file.py | 6 + requirements.txt | 13 +++ .../__pycache__/file_service.cpython-313.pyc | Bin 0 -> 3555 bytes services/file_service.py | 58 +++++++++ 13 files changed, 320 insertions(+) create mode 100644 app.py create mode 100644 config.py create mode 100644 controllers/__pycache__/auth_controller.cpython-313.pyc create mode 100644 controllers/__pycache__/file_controller.cpython-313.pyc create mode 100644 controllers/auth_controller.py create mode 100644 controllers/file_controller.py create mode 100644 extensions.py create mode 100644 logs/app.log create mode 100644 models/__pycache__/uploaded_file.cpython-313.pyc create mode 100644 models/uploaded_file.py create mode 100644 requirements.txt create mode 100644 services/__pycache__/file_service.cpython-313.pyc create mode 100644 services/file_service.py diff --git a/app.py b/app.py new file mode 100644 index 0000000..8d946a7 --- /dev/null +++ b/app.py @@ -0,0 +1,37 @@ +import os +import logging +from flask import Flask +from config import Config +from extensions import db +from controllers.auth_controller import auth_bp + +def create_app(): + app = Flask(__name__) + app.config.from_object(Config) + + # Initialize db with app + db.init_app(app) + + # Configure logging + 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' + ) + + # Register blueprints + from controllers.file_controller import file_bp + app.register_blueprint(file_bp) + app.register_blueprint(auth_bp) + + # Create tables + with app.app_context(): + db.create_all() + + return app + +app = create_app() + +if __name__ == '__main__': + app.run(debug=True, host='127.0.0.1', port=5000) diff --git a/config.py b/config.py new file mode 100644 index 0000000..b0e8885 --- /dev/null +++ b/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/controllers/__pycache__/auth_controller.cpython-313.pyc b/controllers/__pycache__/auth_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d26c9ae13f7b60deed692e48b100c6f4ace88324 GIT binary patch literal 1433 zcmaJ>&u`OK9DjCx*h$)WkZtK&Iv!<14O7Wzmu-};T1&e%1p>+=Y$BB`FLn|$$IhN@ zT5`%xo4Bl#Hi=`8aO1$C5*Lmf&0J~7L!#3V5;vy&1AEW0(+Ht`4}RbGzVG|_e!f4R zd!wUCK%|h0sNDzv|Ike+u|VlHiSj!@05RtQrYn1%S!Xf39>J0ID2_s~!A=1b*@zX` zz7LM0DCSTM^BeJUCImcKQ5o+}4vaP20UE7OyBwbdpm*}sjXXiTcyk<01-HTqngs$Wmk83Gb(~!;D z0RaG_WJ59ry)A}jR~^{OK@NihqD&1q>YxIq1CulYNKGo>EJ2XtL}&ZD^dSIMitkIA zSY8)etpf3)0zoy=*9bxs#Xe?23yOcjen8?OhPFZ{kr3-^Y=x=%eWQN!-vS`b0#ISA zgil}mGeUESMLM({tb_$uVbWlZ4I{qBg&*RQpj?LV-keqPfALa7cq0@qH(K&|+f=YF zwG@wUOQyMLV7YVb0&O6aEK6y%Ex4&#P1sURQ`H-=t}2>r(x#fG)KEH8mB1g8L4Y9y zl&}Kn)TmgI)ooJi&IGb+HR4paHRulrtQ#28=<~4iRzIRSxbpH5DOBg^SCXnJa&SN7 zGvs%qsNaw@RrYac7LZ_uB-CF?+#HYTEO>lFu|zt)9!H{+^co(wDPf%`n0D1mUtPLT zToEg1wOm@dxEk&+FCp~JrSggwRrR_--bGZFED6(I<5BW4=@dfyAaH@UNi}{7YV+4{8nE8Nwy5M>0@SmxP zKT;EJYO=fXA|-6k?_6;+xjWZ(EqCVJQ(pK*xFtAaXZQGpr^36p6VCCko$Q(`6d%nw zLdoGv`$A@W{jqSgyY^(D?Q_C!i!OI;r+NRBd*VxqGIDFg$Hf&4rOK}(A_vm?+p7RSWL$)<#5x-Bi^bwmy z@_7|u80IgKegoOhVFyh(w-vwOyxjTs$`37@OR& zBS2ayO&?18vU%8*SZSq2?QTWemD;B^eQIG_Y5O*oQ&G*b-Bx|d8xb$7_NC|CnK6by zR^3XyGH343xp(f|`+eUz?9|qJ2$ZjUJF`BCkbh#sE}~W0Y2yfak0?aprU*5k9OaB`3r)npC)E6Xogf^l$ z@5mQ;^If#=PFfu7zRLSZnhP~N!+lMM9EN*5TU6(1CRZ?ARJ~tRHK@F$<#U0{rd#k=^JCxY1Z;JYwBJr2{G>;FpjIiP>0yJ6D80 z?gI2GbDFVM+c#Qy*WUBh_>;h?oDdc|tY?Z#kxRl!lHx+1X~TgS!28y; zL|R>{o6ZNZCujmfO+I@Jmt}ChOZ6s*f?9EA2AosHY)GJZUffFD*N_{4FcD%l@;= z63o@x2cOLT{@(BI=}K%A1%AJ?wzAEBvk6ooj%%*vW_C{lHRqRX8c<6T|+|V@`Ye3U(6*l z@4^wHNY^H`c%o29%*@WIxx$k7x>^WYq@$=(AqbxEFhmF;bkPvfYQdx*N-1qYgYrR7 zpgD;q2nNE685c51MkR_s&`|e*IkW?wh6F2_xbKE$6OR7?4C3AeV3Ut;%BM>5sZIHG zNj|-rF3Ua3j=#x%iv+Lg$1j%sLr>1@{xQ8~Y)d}668P}M`zL-~D9NL`H2MN+y{qBX z^LpbePo>VUMQt1G-=@HuPY^Zyvz zxLy3d!Nn@k?I5gPzkY;u$|E?%~;IxCDeAOoD6{ zguE>DP(-MSd^}theJF}OozHp1M-pE09 zy+s5YYNW@&frSjn=rC`hc5WCZ5&@X1<_ zouirTVKK`I$rk*JVrGg`Jf2I;sqr{PnKMM1FBVjmb5RUH6!}K+oPJJrz!ED;jl%N9 ztU5+7K_8-0`wf_Ff#bN(iT88j`;uHNlZ#)Hi4vLkf}Hz;9Q%jI|M<0)g|esfq3iFQ z^uyNgw=OTP4z6}SY}L80=e&pO-w_Dky^Xc)ql9;(i!OJp71z?}Gx(gBOrKY`v9=vJ f(8pczf!cOsmCwT3Te!~9bKYtC46_;?g{}Vwv!fjN literal 0 HcmV?d00001 diff --git a/controllers/auth_controller.py b/controllers/auth_controller.py new file mode 100644 index 0000000..944fe04 --- /dev/null +++ b/controllers/auth_controller.py @@ -0,0 +1,26 @@ +import logging +from flask import Blueprint, request, jsonify + +auth_bp = Blueprint('auth_bp', __name__) + +# Temporary static credentials +VALID_USERNAME = "zander" +VALID_PASSWORD = "zander123" + +@auth_bp.route('/login', methods=['POST']) +def login(): + data = request.get_json() + + if not data or 'username' not in data or 'password' not in data: + logging.warning("Login attempt with missing fields") + return jsonify({"message": "Username and password are required"}), 400 + + username = data['username'] + password = data['password'] + + if username == VALID_USERNAME and password == VALID_PASSWORD: + logging.info(f"Successful login for user: {username}") + return jsonify({"message": "Login successful"}), 200 + else: + logging.warning(f"Failed login attempt for user: {username}") + return jsonify({"message": "Invalid username or password"}), 401 diff --git a/controllers/file_controller.py b/controllers/file_controller.py new file mode 100644 index 0000000..be84dc0 --- /dev/null +++ b/controllers/file_controller.py @@ -0,0 +1,53 @@ +import uuid +import logging +from flask import Blueprint, request, jsonify, send_file +from services.file_service import save_file, update_file, delete_file +from models.uploaded_file import UploadedFile +from config import Config + +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"{Config.UPLOAD_FOLDER}/{uuid.uuid4().hex}" + 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 + 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, 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 diff --git a/extensions.py b/extensions.py new file mode 100644 index 0000000..f0b13d6 --- /dev/null +++ b/extensions.py @@ -0,0 +1,3 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() diff --git a/logs/app.log b/logs/app.log new file mode 100644 index 0000000..763716f --- /dev/null +++ b/logs/app.log @@ -0,0 +1,110 @@ +2025-10-18 11:28:33,106 [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 11:28:33,107 [INFO] Press CTRL+C to quit +2025-10-18 11:28:33,108 [INFO] * Restarting with stat +2025-10-18 11:28:33,731 [WARNING] * Debugger is active! +2025-10-18 11:28:33,768 [INFO] * Debugger PIN: 125-425-247 +2025-10-18 11:31:34,041 [INFO] 127.0.0.1 - - [18/Oct/2025 11:31:34] "POST /upload HTTP/1.1" 400 - +2025-10-18 11:37:48,094 [INFO] 127.0.0.1 - - [18/Oct/2025 11:37:48] "POST /upload HTTP/1.1" 400 - +2025-10-18 11:39:04,317 [INFO] 127.0.0.1 - - [18/Oct/2025 11:39:04] "POST /upload HTTP/1.1" 400 - +2025-10-18 11:40:22,064 [INFO] File saved: uploads/252487abf6e14a39bfa40b72412da69e/1aec644281434ee2a219a2f2f8e3a5b1_23790439261-2.pdf +2025-10-18 11:40:22,072 [INFO] 127.0.0.1 - - [18/Oct/2025 11:40:22] "POST /upload HTTP/1.1" 201 - +2025-10-18 11:47:02,491 [INFO] 127.0.0.1 - - [18/Oct/2025 11:47:02] "GET /file/1 HTTP/1.1" 200 - +2025-10-18 11:48:51,208 [ERROR] Update error: name 'app' is not defined +2025-10-18 11:48:51,210 [INFO] 127.0.0.1 - - [18/Oct/2025 11:48:51] "PUT /file/1 HTTP/1.1" 400 - +2025-10-18 11:49:15,582 [ERROR] Update error: name 'app' is not defined +2025-10-18 11:49:15,583 [INFO] 127.0.0.1 - - [18/Oct/2025 11:49:15] "PUT /file/1 HTTP/1.1" 400 - +2025-10-18 11:52:23,076 [INFO] * Detected change in '/Users/Zander/flask_file_upload_mvc/controllers/file_controller.py', reloading +2025-10-18 11:52:23,477 [INFO] * Restarting with stat +2025-10-18 11:52:24,544 [WARNING] * Debugger is active! +2025-10-18 11:52:24,573 [INFO] * Debugger PIN: 125-425-247 +2025-10-18 11:52:25,660 [INFO] * Detected change in '/Users/Zander/flask_file_upload_mvc/controllers/file_controller.py', reloading +2025-10-18 11:52:25,849 [INFO] * Restarting with stat +2025-10-18 11:52:26,589 [WARNING] * Debugger is active! +2025-10-18 11:52:26,606 [INFO] * Debugger PIN: 125-425-247 +2025-10-18 11:52:57,097 [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 11:52:57,098 [INFO] Press CTRL+C to quit +2025-10-18 11:52:57,099 [INFO] * Restarting with stat +2025-10-18 11:52:57,766 [WARNING] * Debugger is active! +2025-10-18 11:52:57,788 [INFO] * Debugger PIN: 125-425-247 +2025-10-18 11:53:08,816 [INFO] File saved: uploads/7d00800752d64e77bce7e5cb0a37a406_ERD_Dormitally.pdf +2025-10-18 11:53:08,831 [INFO] File updated: ID=1 +2025-10-18 11:53:08,834 [INFO] 127.0.0.1 - - [18/Oct/2025 11:53:08] "PUT /file/1 HTTP/1.1" 200 - +2025-10-18 11:53:41,896 [INFO] File deleted from storage: uploads/7d00800752d64e77bce7e5cb0a37a406_ERD_Dormitally.pdf +2025-10-18 11:53:41,901 [INFO] Record deleted: ID=1 +2025-10-18 11:53:41,902 [INFO] 127.0.0.1 - - [18/Oct/2025 11:53:41] "DELETE /file/1 HTTP/1.1" 200 - +2025-10-19 21:53:39,376 [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-19 21:53:39,376 [INFO] Press CTRL+C to quit +2025-10-19 21:53:39,378 [INFO] * Restarting with stat +2025-10-19 21:53:39,891 [WARNING] * Debugger is active! +2025-10-19 21:53:39,924 [INFO] * Debugger PIN: 820-901-174 +2025-10-19 22:00:48,564 [INFO] File saved: uploads/7f799bf24a634687a65c7b953243be31/90c1b158498d4b4783853be14fd6035b_ERD_Dormitally.pdf +2025-10-19 22:00:48,570 [INFO] 127.0.0.1 - - [19/Oct/2025 22:00:48] "POST /upload HTTP/1.1" 201 - +2025-10-19 22:09:16,689 [INFO] 127.0.0.1 - - [19/Oct/2025 22:09:16] "GET /3 HTTP/1.1" 404 - +2025-10-19 22:09:48,774 [INFO] 127.0.0.1 - - [19/Oct/2025 22:09:48] "GET /file/3 HTTP/1.1" 200 - +2025-10-19 22:10:14,576 [INFO] File saved: uploads/04a938cda90548a8bb5b55b271b5b2b1_23790439261.pdf +2025-10-19 22:10:14,594 [INFO] File updated: ID=3 +2025-10-19 22:10:14,597 [INFO] 127.0.0.1 - - [19/Oct/2025 22:10:14] "PUT /file/3 HTTP/1.1" 200 - +2025-10-19 22:10:28,744 [INFO] File deleted from storage: uploads/04a938cda90548a8bb5b55b271b5b2b1_23790439261.pdf +2025-10-19 22:10:28,749 [INFO] Record deleted: ID=3 +2025-10-19 22:10:28,750 [INFO] 127.0.0.1 - - [19/Oct/2025 22:10:28] "DELETE /file/3 HTTP/1.1" 200 - +2025-10-30 14:43:42,381 [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-30 14:43:42,382 [INFO] Press CTRL+C to quit +2025-10-30 14:43:42,383 [INFO] * Restarting with stat +2025-10-30 14:43:43,002 [WARNING] * Debugger is active! +2025-10-30 14:43:43,036 [INFO] * Debugger PIN: 820-901-174 +2025-10-30 14:45:10,604 [INFO] * Detected change in '/Users/Zander/flask_file_upload_mvc/app.py', reloading +2025-10-30 14:45:10,876 [INFO] * Restarting with stat +2025-10-30 14:46:02,643 [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-30 14:46:02,643 [INFO] Press CTRL+C to quit +2025-10-30 14:46:02,646 [INFO] * Restarting with stat +2025-10-30 14:46:03,177 [WARNING] * Debugger is active! +2025-10-30 14:46:03,195 [INFO] * Debugger PIN: 820-901-174 +2025-10-30 14:46:25,448 [INFO] * Detected change in '/Users/Zander/flask_file_upload_mvc/app.py', reloading +2025-10-30 14:46:25,667 [INFO] * Restarting with stat +2025-10-30 14:46:26,438 [WARNING] * Debugger is active! +2025-10-30 14:46:26,473 [INFO] * Debugger PIN: 820-901-174 +2025-10-30 14:46:27,519 [INFO] * Detected change in '/Users/Zander/flask_file_upload_mvc/app.py', reloading +2025-10-30 14:46:27,700 [INFO] * Restarting with stat +2025-10-30 14:46:28,374 [WARNING] * Debugger is active! +2025-10-30 14:46:28,394 [INFO] * Debugger PIN: 820-901-174 +2025-10-30 14:46:39,526 [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-30 14:46:39,526 [INFO] Press CTRL+C to quit +2025-10-30 14:47:39,099 [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-30 14:47:39,099 [INFO] Press CTRL+C to quit +2025-10-30 14:47:39,101 [INFO] * Restarting with stat +2025-10-30 14:47:39,755 [WARNING] * Debugger is active! +2025-10-30 14:47:39,777 [INFO] * Debugger PIN: 820-901-174 +2025-10-30 14:48:49,220 [INFO] 127.0.0.1 - - [30/Oct/2025 14:48:49] "POST /login HTTP/1.1" 415 - +2025-10-30 14:49:01,353 [INFO] 127.0.0.1 - - [30/Oct/2025 14:49:01] "POST /login HTTP/1.1" 415 - +2025-10-30 14:50:54,899 [INFO] 127.0.0.1 - - [30/Oct/2025 14:50:54] "POST /login HTTP/1.1" 415 - +2025-10-30 14:51:41,423 [INFO] Successful login for user: admin +2025-10-30 14:51:41,424 [INFO] 127.0.0.1 - - [30/Oct/2025 14:51:41] "POST /login HTTP/1.1" 200 - +2025-10-30 14:52:32,451 [INFO] * Detected change in '/Users/Zander/flask_file_upload_mvc/controllers/auth_controller.py', reloading +2025-10-30 14:52:32,663 [INFO] * Restarting with stat +2025-10-30 14:52:33,821 [WARNING] * Debugger is active! +2025-10-30 14:52:33,836 [INFO] * Debugger PIN: 820-901-174 +2025-10-30 14:52:37,912 [INFO] * Detected change in '/Users/Zander/flask_file_upload_mvc/controllers/auth_controller.py', reloading +2025-10-30 14:52:38,290 [INFO] * Restarting with stat +2025-10-30 14:52:38,940 [WARNING] * Debugger is active! +2025-10-30 14:52:38,958 [INFO] * Debugger PIN: 820-901-174 +2025-10-30 14:57:09,956 [INFO] Successful login for user: zander +2025-10-30 14:57:09,960 [INFO] 127.0.0.1 - - [30/Oct/2025 14:57:09] "POST /login HTTP/1.1" 200 - +2025-10-30 14:58:03,175 [WARNING] Failed login attempt for user: zander +2025-10-30 14:58:03,175 [INFO] 127.0.0.1 - - [30/Oct/2025 14:58:03] "POST /login HTTP/1.1" 401 - +2025-10-30 15:00:39,059 [INFO] File saved: uploads/c46cafea63d04260a1fa75b93596d34e/d31965e3bd2a4ccebe7ff24bd4ed9f95_ERD_Dormitally.pdf +2025-10-30 15:00:39,071 [INFO] 127.0.0.1 - - [30/Oct/2025 15:00:39] "POST /upload HTTP/1.1" 201 - +2025-10-30 15:01:33,801 [INFO] 127.0.0.1 - - [30/Oct/2025 15:01:33] "GET /file/5 HTTP/1.1" 200 - +2025-10-30 15:01:39,525 [INFO] 127.0.0.1 - - [30/Oct/2025 15:01:39] "GET /file/6 HTTP/1.1" 404 - +2025-10-30 15:02:39,355 [INFO] File saved: uploads/b6d734cd5425426290025e385be8f33d_Untitled_Diagram.drawio.png +2025-10-30 15:02:39,372 [INFO] File updated: ID=5 +2025-10-30 15:02:39,374 [INFO] 127.0.0.1 - - [30/Oct/2025 15:02:39] "PUT /file/5 HTTP/1.1" 200 - +2025-10-30 15:02:53,039 [INFO] File deleted from storage: uploads/b6d734cd5425426290025e385be8f33d_Untitled_Diagram.drawio.png +2025-10-30 15:02:53,047 [INFO] Record deleted: ID=5 +2025-10-30 15:02:53,048 [INFO] 127.0.0.1 - - [30/Oct/2025 15:02:53] "DELETE /file/5 HTTP/1.1" 200 - diff --git a/models/__pycache__/uploaded_file.cpython-313.pyc b/models/__pycache__/uploaded_file.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8a7d79445d7c92e596cd7b8e3af9274b682b46e GIT binary patch literal 806 zcmbtR&ubGw6rS1L{MaNVX+dpz5Gi=rOCxF^jpD&Vu!=o&>_q~@COb)&PB!Vxu9#av z@X$-GP+HnUd(>P15+NtUfOznfn-D$gn`~nh!HWYk^Sy80kMF%1&&*^HtdGg@=4ZIS zXk)q(7hqfkaDWIR_z8N91-^mi(IO(o0wQJ|uUpfA%e!pGSeR|gNsP4NU}boX=A{op zzY2T+_5jt9zyt|{U}4s+^@*_G-4PT8KKCc5En*OJ*$}CJL>2yyf)=bYj(L(GNyuRT zcZLi}iEKOV*ebWt<6YNdUd`uhqzCd0W{~X(zvGHN-D3UG4{W?B@oHDEX54-ChtqVzR*8BIkY-igX1+C+S$G0Zt>0S!|HzZ-Q9tG zYv^L!uMu8+6GO!FQ!7RWs+7-=+ z+GS=*OIDGD%0+E1QCb9+PbScdd-JJ(MGoAM1%0qG$>w+^YidB|K*k@h7}c%LxhUK*9%gS5}Dmf1t-L!~{EJ zYRe^}rB`x!P0y@pFce4@@fXCfXii7V^2G?amwP8nMH-@7%e&IP-3LX6gNzg~Z(5g#>(@ z#6R4myQ22Z$)u?fbMjtBS2Z%ZlFyi{%sA;1vr2mH!SW=mc#vDx%t^XA-Rw;i9@>FS zo*t&M9i#^|(tq5%3{@HJ$-$kmKiz7`i@O_7K7IV@Q}fyF+TgWkA6bLf8-us3!CSS# z+x3}6YjClO7Y|_%TIOy+JJcG?uznc*3Z+`+158}>#HDsSgp%;5B*g2kC`y42r<{DI?n(vtN+rmpb~}XlU#OgFk8&x95;*A!qAQ{< zcHAw@=i99kY0F$%>fsKgW4L7>A8)rw%)cGJXn>C|y^(NVcj@lb89t)C#-(TK7PP2t6r)Wq ze4qNawvjW7X=BwE(lHn527R`Vxosg+yuTS>8_moEO}!C~^)Q!p7Wsdh$(OVlLJVTN z4AYj@GOL=JBPO9fXyZ~Tr`m2>zhR5_wGA6ng%A&noNi;Pkx)7biyU54GtHc#+hRsl zZO^i?wgzB&^F}tCgGhpN`ic?r*qC0-mOsdt5Ri;K0O;IBF6p_CN?Mu*DBDMMr`chW zuB|iFOzOc=RtR;I<|3+&T!elX{LH^YRYv=A-!6XA^|-4ck6QBRez3nfaJd$os!CG_ z*s~q~EWRUG)Nk>=mc@dvfpUli z&~?gjkw9rt&sQM`zub#-F)6+hBOo<7<&_xGiVO-%VtkF$D%8M7N>V(Ie10lFF%yNT z)Yg4WG=TVXq9)*m23l5@!X4~mg_`Y1Ns9T_{ge2(8^Vf~1ya3yyVB34cB`Ci%UtT9 zxfMN>b9}bl0?n=@IzqF^QrOQy1|m+b~B$U|Gq zYDGc=8KKdK&=6&N82Bck_l8uctrt%WAa zv){@+jK5edc%dp?_|bzT|MvRU`t~PVpX}Uk^p07*V-0W2^2YWh{};Z`eLG`~@URsg zu1g~a-Dh^+taVS7=lA-D8vWx||9E+}jw5>mzitd%vIcIN~v(^7GgjCa>RsW>-EFZaLd7dA!5RD4*YM3{pNo_>_km~SS$G+2NiSXV>P_$ z;Qv2P$<{Lk?r<`Uzk>hlNa=v_zujLv3S$<<@B*0tjhkl~f2x+(fWPPpG1j7H(IANq4fd*g@|GOwf}#df{^pG>!0>6&`!qR|`*-(bc16#63Yd7#=i{p_vAm09b`Y;}6B8kw(4ahgCOzd8AAXSI%p zj`FwfWVmY2Xl1>Ut9D=h2cBZtEf#cQ7Y&-s9(E8O?W>zwDLYXr=JL&F$LTc#KGmFX z9^?F>O;NS|+D1{+=_|u@%t&*I^Ppj2k47XyZSyz8PIN3M-2{!Ly!H;c1yhuNll~?D x6a_(ej(pFN{2T>epbIb1r59-6yU6t_3ctjHuq3?ngoM*ChoCwfl7(re^Iv5Ub|nA+ literal 0 HcmV?d00001 diff --git a/services/file_service.py b/services/file_service.py new file mode 100644 index 0000000..663628b --- /dev/null +++ b/services/file_service.py @@ -0,0 +1,58 @@ +import os +import uuid +import logging +from werkzeug.utils import secure_filename +from models.uploaded_file import UploadedFile +from extensions import db + +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'pdf', 'txt'} + +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + +def save_file(file, base_folder): + if not allowed_file(file.filename): + raise ValueError('File type not allowed') + + os.makedirs(base_folder, exist_ok=True) + filename = secure_filename(file.filename) + unique_name = f"{uuid.uuid4().hex}_{filename}" + save_path = os.path.join(base_folder, unique_name) + file.save(save_path) + + new_file = UploadedFile(filename=unique_name, file_path=save_path) + db.session.add(new_file) + db.session.commit() + + logging.info(f"File saved: {save_path}") + return new_file + +def update_file(file_id, new_file, base_folder): + existing = UploadedFile.query.get(file_id) + if not existing: + raise ValueError('File not found') + + # remove old file if exists + if os.path.exists(existing.file_path): + os.remove(existing.file_path) + + new_record = save_file(new_file, base_folder) + existing.filename = new_record.filename + existing.file_path = new_record.file_path + db.session.commit() + + logging.info(f"File updated: ID={file_id}") + return existing + +def delete_file(file_id): + file_record = UploadedFile.query.get(file_id) + if not file_record: + raise ValueError('File not found') + + if os.path.exists(file_record.file_path): + os.remove(file_record.file_path) + logging.info(f"File deleted from storage: {file_record.file_path}") + + db.session.delete(file_record) + db.session.commit() + logging.info(f"Record deleted: ID={file_id}")