diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e3315a --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ + +.env + +__pycache__/ +*.pyc + +logs/ + +tempCodeRunnerFile.py + +venv/ diff --git a/app.py b/app.py new file mode 100644 index 0000000..4fd3f38 --- /dev/null +++ b/app.py @@ -0,0 +1,32 @@ +import os +import logging +from flask import Flask +from config import Config +from extensions import db + + +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) + +from controllers.auth_controller import auth_bp +app.register_blueprint(auth_bp) + + +with app.app_context(): + db.create_all() + +if __name__ == '__main__': + app.run(debug=True) diff --git a/config.py b/config.py new file mode 100644 index 0000000..10c3d02 --- /dev/null +++ b/config.py @@ -0,0 +1,13 @@ +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', 'localhost')}:{os.getenv('DB_PORT', '3306')}/{os.getenv('DB_NAME', 'flask_uploads_db')}" + ) + SQLALCHEMY_TRACK_MODIFICATIONS = False + UPLOAD_FOLDER = os.getenv('UPLOAD_FOLDER', 'uploads') + LOG_FOLDER = 'logs' + DEBUG = True \ No newline at end of file diff --git a/controllers/auth_controller.py b/controllers/auth_controller.py new file mode 100644 index 0000000..6961de3 --- /dev/null +++ b/controllers/auth_controller.py @@ -0,0 +1,26 @@ +from flask import Blueprint, request, jsonify, current_app +import logging + +auth_bp = Blueprint("auth", __name__, url_prefix="/auth") + +VALID_USERNAME = "stephen" # AT VAUGHKET HENDE?! +VALID_PASSWORD = "shetkatalagaballabyu" + +@auth_bp.route("/login", methods=["POST"]) +def login(): + if not request.is_json: + current_app.logger.warning("Login failed: Non-JSON request received") + return jsonify({"error": "Request must be in JSON format"}), 400 + + data = request.get_json() + username = data.get("username") + password = data.get("password") + + current_app.logger.info(f"Login attempt by user: {username}") + + if username == VALID_USERNAME and password == VALID_PASSWORD: + current_app.logger.info(f"Login successful for user: {username}") + return jsonify({"message": "Login successful!"}), 200 + else: + current_app.logger.warning(f"Login failed for user: {username}") + return jsonify({"error": "Invalid username or password"}), 401 diff --git a/controllers/file_controller.py b/controllers/file_controller.py new file mode 100644 index 0000000..11d28ef --- /dev/null +++ b/controllers/file_controller.py @@ -0,0 +1,68 @@ +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 +from flask import send_from_directory +import logging +import uuid + +file_bp = Blueprint('file_bp', __name__) + + +# POST — Upload file +@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}" # ✅ use current_app + 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 + +# GET — Retrieve file info or download +@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 + +# PUT — Update file +@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']) # ✅ use current_app + 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 + +# DELETE — Delete file +@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..76e7ae0 --- /dev/null +++ b/extensions.py @@ -0,0 +1,4 @@ +# extensions.py +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() diff --git a/models/uploaded_file.py b/models/uploaded_file.py new file mode 100644 index 0000000..59a1427 --- /dev/null +++ b/models/uploaded_file.py @@ -0,0 +1,7 @@ +from extensions import db + +# This model represents an uploaded file record in the database +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/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a2fbb49 Binary files /dev/null and b/requirements.txt differ diff --git a/services/file_service.py b/services/file_service.py new file mode 100644 index 0000000..0156896 --- /dev/null +++ b/services/file_service.py @@ -0,0 +1,47 @@ +import os +import uuid +from werkzeug.utils import secure_filename +from models.uploaded_file import UploadedFile +from extensions 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 +def update_file(file_id, new_file, base_folder): + 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) + 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}") diff --git a/uploads/0ed4567895114472b0502088500d92db/99be833dd2804f2c96089138ef3da9d0_webcam-toy-photo2.jpg b/uploads/0ed4567895114472b0502088500d92db/99be833dd2804f2c96089138ef3da9d0_webcam-toy-photo2.jpg new file mode 100644 index 0000000..0259d78 Binary files /dev/null and b/uploads/0ed4567895114472b0502088500d92db/99be833dd2804f2c96089138ef3da9d0_webcam-toy-photo2.jpg differ diff --git a/uploads/1aa3071adca34d88bb43f57241012440_webcam-toy-photo31.jpg b/uploads/1aa3071adca34d88bb43f57241012440_webcam-toy-photo31.jpg new file mode 100644 index 0000000..a4da7bf Binary files /dev/null and b/uploads/1aa3071adca34d88bb43f57241012440_webcam-toy-photo31.jpg differ diff --git a/uploads/34a3629492334a23bde2363ef20d021f/023908a96e8e431e9783fdd029576bec_home.png b/uploads/34a3629492334a23bde2363ef20d021f/023908a96e8e431e9783fdd029576bec_home.png new file mode 100644 index 0000000..a6b446a Binary files /dev/null and b/uploads/34a3629492334a23bde2363ef20d021f/023908a96e8e431e9783fdd029576bec_home.png differ diff --git a/uploads/5bca4fc65331487dbb472081892bfcf2/7a76d016075e4c15ba2c2c423b8bd96b_home.png b/uploads/5bca4fc65331487dbb472081892bfcf2/7a76d016075e4c15ba2c2c423b8bd96b_home.png new file mode 100644 index 0000000..a6b446a Binary files /dev/null and b/uploads/5bca4fc65331487dbb472081892bfcf2/7a76d016075e4c15ba2c2c423b8bd96b_home.png differ diff --git a/uploads/61ad8f02c32243aeb79b348b60874a32/23dd49f8fac34faeb7936938eac51eda_logout.png b/uploads/61ad8f02c32243aeb79b348b60874a32/23dd49f8fac34faeb7936938eac51eda_logout.png new file mode 100644 index 0000000..245f55b Binary files /dev/null and b/uploads/61ad8f02c32243aeb79b348b60874a32/23dd49f8fac34faeb7936938eac51eda_logout.png differ diff --git a/uploads/8cd3476685c44d888f5416ef9ffe2a09_shirt.png b/uploads/8cd3476685c44d888f5416ef9ffe2a09_shirt.png new file mode 100644 index 0000000..9a5ff54 Binary files /dev/null and b/uploads/8cd3476685c44d888f5416ef9ffe2a09_shirt.png differ diff --git a/uploads/abced3fd088f4c96bda6a8278e55d4a0/3d6917a734fc4a45b4d5444b702b6528_home.png b/uploads/abced3fd088f4c96bda6a8278e55d4a0/3d6917a734fc4a45b4d5444b702b6528_home.png new file mode 100644 index 0000000..a6b446a Binary files /dev/null and b/uploads/abced3fd088f4c96bda6a8278e55d4a0/3d6917a734fc4a45b4d5444b702b6528_home.png differ diff --git a/uploads/e0ae8438c12a472199a2fa201718072c/39642bd3f16749ee92ca236fa2404e8d_webcam-toy-photo25.jpg b/uploads/e0ae8438c12a472199a2fa201718072c/39642bd3f16749ee92ca236fa2404e8d_webcam-toy-photo25.jpg new file mode 100644 index 0000000..bbb2fd8 Binary files /dev/null and b/uploads/e0ae8438c12a472199a2fa201718072c/39642bd3f16749ee92ca236fa2404e8d_webcam-toy-photo25.jpg differ diff --git a/uploads/fb8abe48d4a44cccb63e7161798039af/e17544ef18b148e7920ab9def6dd1bbc_logout.png b/uploads/fb8abe48d4a44cccb63e7161798039af/e17544ef18b148e7920ab9def6dd1bbc_logout.png new file mode 100644 index 0000000..245f55b Binary files /dev/null and b/uploads/fb8abe48d4a44cccb63e7161798039af/e17544ef18b148e7920ab9def6dd1bbc_logout.png differ diff --git a/uploads/fd1a5222de044c6ca61652489ce3707d/edcaf93a85f84704b9907996a93dd7c2_home.png b/uploads/fd1a5222de044c6ca61652489ce3707d/edcaf93a85f84704b9907996a93dd7c2_home.png new file mode 100644 index 0000000..a6b446a Binary files /dev/null and b/uploads/fd1a5222de044c6ca61652489ce3707d/edcaf93a85f84704b9907996a93dd7c2_home.png differ