Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions flask_file_upload_mvc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.venv/
venv/
env/
ENV/

.env
.env.*
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
29 changes: 29 additions & 0 deletions flask_file_upload_mvc/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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
from controllers.auth_controller import auth_bp

app.register_blueprint(file_bp)
app.register_blueprint(auth_bp)

with app.app_context():
db.create_all()

if __name__ == '__main__':
app.run(debug=True)
18 changes: 18 additions & 0 deletions flask_file_upload_mvc/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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')


SECRET_KEY = os.getenv('SECRET_KEY')

LOG_FOLDER = 'logs'
DEBUG = True
Binary file not shown.
Binary file not shown.
Binary file not shown.
39 changes: 39 additions & 0 deletions flask_file_upload_mvc/controllers/auth_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from flask import Blueprint, request, jsonify
import logging
import jwt
from datetime import datetime, timedelta
from config import Config

auth_bp = Blueprint('auth_bp', __name__)

USERNAME = "test123"
PASSWORD = "password123"

@auth_bp.route('/login', methods=['POST'])
def login():
try:
data = request.get_json()
username = data.get('username')
password = data.get('password')


if not username or not password:
logging.warning(f"{datetime.now()} Empty fields")
return jsonify({'message': 'Username and password'}), 400

if username == USERNAME and password == PASSWORD:
token = jwt.encode({
'username': username,
'exp': datetime.now() + timedelta(minutes=20)
}, Config.SECRET_KEY, algorithm='HS256')
logging.info(f"[LOGIN SUCCESS!] username {username}")
return jsonify({'message': 'Login Successful',
'username': username,
'token': token
}), 200
logging.warning(f"{datetime.now()} Invalid credentials for {username}")
return jsonify({'message': 'Invalid credentials'}), 401

except Exception as e:
logging.error(f"Login error : {e}")
return jsonify({'message': str(e)}), 500
59 changes: 59 additions & 0 deletions flask_file_upload_mvc/controllers/file_controller.py
Original file line number Diff line number Diff line change
@@ -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/<int:file_id>', 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/<id>
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/<int:file_id>', 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/<int:file_id>', 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
3 changes: 3 additions & 0 deletions flask_file_upload_mvc/extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
41 changes: 41 additions & 0 deletions flask_file_upload_mvc/logs/app.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
2025-11-08 02:57:34,668 [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-11-08 02:57:34,668 [INFO] Press CTRL+C to quit
2025-11-08 02:57:34,669 [INFO] * Restarting with stat
2025-11-08 02:57:35,177 [WARNING] * Debugger is active!
2025-11-08 02:57:35,179 [INFO] * Debugger PIN: 670-920-050
2025-11-08 02:58:38,552 [INFO] File saved: uploads/9b21cfac5cad4d5fa28dd898ff41805f/4d5be3ce45384824b2a52625975a98d0_Screenshot_from_2025-08-27_00-01-34.png
2025-11-08 02:58:38,562 [INFO] 127.0.0.1 - - [08/Nov/2025 02:58:38] "POST /upload HTTP/1.1" 201 -
2025-11-08 02:58:48,702 [INFO] 127.0.0.1 - - [08/Nov/2025 02:58:48] "GET /upload/1 HTTP/1.1" 404 -
2025-11-08 02:59:00,770 [INFO] 127.0.0.1 - - [08/Nov/2025 02:59:00] "GET /file/1 HTTP/1.1" 200 -
2025-11-08 02:59:30,151 [INFO] File updated: ID=1
2025-11-08 02:59:30,154 [INFO] 127.0.0.1 - - [08/Nov/2025 02:59:30] "PUT /file/1 HTTP/1.1" 200 -
2025-11-08 02:59:36,080 [INFO] 127.0.0.1 - - [08/Nov/2025 02:59:36] "GET /file/1 HTTP/1.1" 200 -
2025-11-08 02:59:51,076 [INFO] File deleted from storage: uploads/9b21cfac5cad4d5fa28dd898ff41805f/b8ed57cb4a2a4149ac376120ccce28d5_Screenshot_from_2025-08-29_22-03-40.png
2025-11-08 02:59:51,109 [INFO] Record deleted: ID=1
2025-11-08 02:59:51,111 [INFO] 127.0.0.1 - - [08/Nov/2025 02:59:51] "DELETE /file/1 HTTP/1.1" 200 -
2025-11-08 02:59:54,698 [INFO] 127.0.0.1 - - [08/Nov/2025 02:59:54] "GET /file/1 HTTP/1.1" 404 -
2025-11-08 03:00:06,283 [WARNING] 2025-11-08 03:00:06.283345 Empty fields
2025-11-08 03:00:06,283 [INFO] 127.0.0.1 - - [08/Nov/2025 03:00:06] "POST /login HTTP/1.1" 400 -
2025-11-08 03:00:09,134 [WARNING] 2025-11-08 03:00:09.134666 Invalid credentials for test123
2025-11-08 03:00:09,135 [INFO] 127.0.0.1 - - [08/Nov/2025 03:00:09] "POST /login HTTP/1.1" 401 -
2025-11-08 03:00:20,003 [INFO] [LOGIN SUCCESS!] username test123
2025-11-08 03:00:20,003 [INFO] 127.0.0.1 - - [08/Nov/2025 03:00:20] "POST /login HTTP/1.1" 200 -
2025-11-08 03:00:25,614 [WARNING] 2025-11-08 03:00:25.614643 Invalid credentials for test123
2025-11-08 03:00:25,615 [INFO] 127.0.0.1 - - [08/Nov/2025 03:00:25] "POST /login HTTP/1.1" 401 -
2025-11-08 03:00:35,021 [INFO] [LOGIN SUCCESS!] username test123
2025-11-08 03:00:35,021 [INFO] 127.0.0.1 - - [08/Nov/2025 03:00:35] "POST /login HTTP/1.1" 200 -
2025-11-08 03:00:42,317 [WARNING] 2025-11-08 03:00:42.317665 Invalid credentials for test2123
2025-11-08 03:00:42,318 [INFO] 127.0.0.1 - - [08/Nov/2025 03:00:42] "POST /login HTTP/1.1" 401 -
2025-11-08 03:00:48,099 [INFO] [LOGIN SUCCESS!] username test123
2025-11-08 03:00:48,100 [INFO] 127.0.0.1 - - [08/Nov/2025 03:00:48] "POST /login HTTP/1.1" 200 -
2025-11-08 03:01:42,924 [INFO] [LOGIN SUCCESS!] username test123
2025-11-08 03:01:42,925 [INFO] 127.0.0.1 - - [08/Nov/2025 03:01:42] "POST /login HTTP/1.1" 200 -
2025-11-08 03:01:45,978 [WARNING] 2025-11-08 03:01:45.978766 Invalid credentials for test123s
2025-11-08 03:01:45,979 [INFO] 127.0.0.1 - - [08/Nov/2025 03:01:45] "POST /login HTTP/1.1" 401 -
2025-11-08 03:01:48,768 [INFO] [LOGIN SUCCESS!] username test123
2025-11-08 03:01:48,769 [INFO] 127.0.0.1 - - [08/Nov/2025 03:01:48] "POST /login HTTP/1.1" 200 -
2025-11-08 03:01:51,521 [WARNING] 2025-11-08 03:01:51.521314 Invalid credentials for test123
2025-11-08 03:01:51,522 [INFO] 127.0.0.1 - - [08/Nov/2025 03:01:51] "POST /login HTTP/1.1" 401 -
2025-11-08 03:01:55,454 [INFO] [LOGIN SUCCESS!] username test123
2025-11-08 03:01:55,454 [INFO] 127.0.0.1 - - [08/Nov/2025 03:01:55] "POST /login HTTP/1.1" 200 -
Binary file not shown.
Binary file not shown.
6 changes: 6 additions & 0 deletions flask_file_upload_mvc/models/uploaded_file.py
Original file line number Diff line number Diff line change
@@ -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)
18 changes: 18 additions & 0 deletions flask_file_upload_mvc/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
blinker==1.9.0
cffi==2.0.0
click==8.3.0
colorama==0.4.6
cryptography==46.0.3
Flask==3.1.2
Flask-SQLAlchemy==3.1.1
greenlet==3.2.4
itsdangerous==2.2.0
Jinja2==3.1.6
MarkupSafe==3.0.3
pycparser==2.23
PyJWT==2.10.1
PyMySQL==1.1.2
python-dotenv==1.1.1
SQLAlchemy==2.0.44
typing_extensions==4.15.0
Werkzeug==3.1.3
Binary file not shown.
Binary file not shown.
65 changes: 65 additions & 0 deletions flask_file_upload_mvc/services/file_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import os
import uuid
from werkzeug.utils import secure_filename
from models.uploaded_file import UploadedFile
from models.uploaded_file import db
import logging

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

# Update service reuse the existing folder and remove the bug where after updating it gets added again in the database
def update_file(file_id, new_file, _):
existing = UploadedFile.query.get(file_id)
if not existing:
raise ValueError('File not found')

if os.path.exists(existing.file_path):
os.remove(existing.file_path)

existing_folder = os.path.dirname(existing.file_path)
os.makedirs(existing_folder, exist_ok=True)
filename = secure_filename(new_file.filename)
unique_name = f"{uuid.uuid4().hex}_{filename}"
save_path = os.path.join(existing_folder, unique_name)

new_file.save(save_path)

existing.filename = unique_name
existing.file_path = save_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}")