diff --git a/flask_file_upload_mvc/.gitignore b/flask_file_upload_mvc/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/flask_file_upload_mvc/.gitignore @@ -0,0 +1 @@ +.env diff --git a/flask_file_upload_mvc/.vs/VSWorkspaceState.json b/flask_file_upload_mvc/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..6daf1ee --- /dev/null +++ b/flask_file_upload_mvc/.vs/VSWorkspaceState.json @@ -0,0 +1,12 @@ +{ + "ExpandedNodes": [ + "", + "\\controllers", + "\\controllers\\__pycache__", + "\\models", + "\\services", + "\\uploads" + ], + "SelectedNode": "\\app.py", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/27285a50-d78a-45bc-8587-71ceed15b967.vsidx b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/27285a50-d78a-45bc-8587-71ceed15b967.vsidx new file mode 100644 index 0000000..673080d Binary files /dev/null and b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/27285a50-d78a-45bc-8587-71ceed15b967.vsidx differ diff --git a/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/38b5c44a-e696-40b3-8292-0eec5c49b1ff.vsidx b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/38b5c44a-e696-40b3-8292-0eec5c49b1ff.vsidx new file mode 100644 index 0000000..b3a5cc6 Binary files /dev/null and b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/38b5c44a-e696-40b3-8292-0eec5c49b1ff.vsidx differ diff --git a/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/7716003e-af54-4dc2-8aea-99215f903983.vsidx b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/7716003e-af54-4dc2-8aea-99215f903983.vsidx new file mode 100644 index 0000000..bffecb8 Binary files /dev/null and b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/7716003e-af54-4dc2-8aea-99215f903983.vsidx differ diff --git a/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/917f158e-4d76-47b1-8689-f1817d69a700.vsidx b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/917f158e-4d76-47b1-8689-f1817d69a700.vsidx new file mode 100644 index 0000000..89c39b0 Binary files /dev/null and b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/917f158e-4d76-47b1-8689-f1817d69a700.vsidx differ diff --git a/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/be2fea4f-3e12-4e63-931a-b796639777c8.vsidx b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/be2fea4f-3e12-4e63-931a-b796639777c8.vsidx new file mode 100644 index 0000000..70eb51f Binary files /dev/null and b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/FileContentIndex/be2fea4f-3e12-4e63-931a-b796639777c8.vsidx differ diff --git a/flask_file_upload_mvc/.vs/flask_file_upload_mvc/v17/.wsuo b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/v17/.wsuo new file mode 100644 index 0000000..ecd7d0e Binary files /dev/null and b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/v17/.wsuo differ diff --git a/flask_file_upload_mvc/.vs/flask_file_upload_mvc/v17/DocumentLayout.backup.json b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/v17/DocumentLayout.backup.json new file mode 100644 index 0000000..0a475b7 --- /dev/null +++ b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/v17/DocumentLayout.backup.json @@ -0,0 +1,136 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\models\\uploaded_file.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:models\\uploaded_file.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\app.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:app.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\models\\extensions.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:models\\extensions.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\services\\file_service.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:services\\file_service.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\config.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:config.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\controllers\\file_controller.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:controllers\\file_controller.py||{8B382828-6202-11D1-8870-0000F87579D2}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 1, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedHeight": 311, + "SelectedChildIndex": 3, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "extensions.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\models\\extensions.py", + "RelativeDocumentMoniker": "models\\extensions.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\models\\extensions.py", + "RelativeToolTip": "models\\extensions.py", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:54:33.993Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 5, + "Title": "file_controller.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\controllers\\file_controller.py", + "RelativeDocumentMoniker": "controllers\\file_controller.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\controllers\\file_controller.py", + "RelativeToolTip": "controllers\\file_controller.py", + "ViewState": "AgIAAD8AAAAAAAAAAAAuwEoAAAAnAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:29:53.424Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 3, + "Title": "file_service.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\services\\file_service.py", + "RelativeDocumentMoniker": "services\\file_service.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\services\\file_service.py", + "RelativeToolTip": "services\\file_service.py", + "ViewState": "AgIAACQAAAAAAAAAAAAAACkAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:27:19.887Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "uploaded_file.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\models\\uploaded_file.py", + "RelativeDocumentMoniker": "models\\uploaded_file.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\models\\uploaded_file.py", + "RelativeToolTip": "models\\uploaded_file.py", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:25:46.102Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "app.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\app.py", + "RelativeDocumentMoniker": "app.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\app.py", + "RelativeToolTip": "app.py", + "ViewState": "AgIAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:21:09.174Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 4, + "Title": "config.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\config.py", + "RelativeDocumentMoniker": "config.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\config.py", + "RelativeToolTip": "config.py", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:18:24.643Z", + "EditorCaption": "" + } + ] + }, + { + "DockedHeight": 126, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/flask_file_upload_mvc/.vs/flask_file_upload_mvc/v17/DocumentLayout.json b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/v17/DocumentLayout.json new file mode 100644 index 0000000..eb6b5d7 --- /dev/null +++ b/flask_file_upload_mvc/.vs/flask_file_upload_mvc/v17/DocumentLayout.json @@ -0,0 +1,136 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\app.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:app.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\config.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:config.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\controllers\\file_controller.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:controllers\\file_controller.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\models\\uploaded_file.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:models\\uploaded_file.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\services\\file_service.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:services\\file_service.py||{8B382828-6202-11D1-8870-0000F87579D2}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\extensions.py||{8B382828-6202-11D1-8870-0000F87579D2}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:extensions.py||{8B382828-6202-11D1-8870-0000F87579D2}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 1, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedHeight": 311, + "SelectedChildIndex": 4, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 5, + "Title": "extensions.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\extensions.py", + "RelativeDocumentMoniker": "extensions.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\extensions.py", + "RelativeToolTip": "extensions.py", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:54:33.993Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "file_controller.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\controllers\\file_controller.py", + "RelativeDocumentMoniker": "controllers\\file_controller.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\controllers\\file_controller.py", + "RelativeToolTip": "controllers\\file_controller.py", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:29:53.424Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 4, + "Title": "file_service.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\services\\file_service.py", + "RelativeDocumentMoniker": "services\\file_service.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\services\\file_service.py", + "RelativeToolTip": "services\\file_service.py", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAQAAAA6AAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:27:19.887Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 3, + "Title": "uploaded_file.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\models\\uploaded_file.py", + "RelativeDocumentMoniker": "models\\uploaded_file.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\models\\uploaded_file.py", + "RelativeToolTip": "models\\uploaded_file.py", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:25:46.102Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "app.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\app.py", + "RelativeDocumentMoniker": "app.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\app.py", + "RelativeToolTip": "app.py", + "ViewState": "AgIAADAAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:21:09.174Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "config.py", + "DocumentMoniker": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\config.py", + "RelativeDocumentMoniker": "config.py", + "ToolTip": "C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\config.py", + "RelativeToolTip": "config.py", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.002457|", + "WhenOpened": "2025-10-19T13:18:24.643Z", + "EditorCaption": "" + } + ] + }, + { + "DockedHeight": 126, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/flask_file_upload_mvc/.vs/slnx.sqlite b/flask_file_upload_mvc/.vs/slnx.sqlite new file mode 100644 index 0000000..bb7151a Binary files /dev/null and b/flask_file_upload_mvc/.vs/slnx.sqlite differ 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 0000000..34df76f Binary files /dev/null and b/flask_file_upload_mvc/__pycache__/app.cpython-314.pyc differ 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 0000000..101b1ff Binary files /dev/null and b/flask_file_upload_mvc/__pycache__/config.cpython-314.pyc differ 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 0000000..a73a48d Binary files /dev/null and b/flask_file_upload_mvc/__pycache__/extensions.cpython-314.pyc differ diff --git a/flask_file_upload_mvc/app.py b/flask_file_upload_mvc/app.py new file mode 100644 index 0000000..2078a35 --- /dev/null +++ b/flask_file_upload_mvc/app.py @@ -0,0 +1,67 @@ +import os +import logging +from flask import Flask +from config import Config +from extensions import db # import db from extensions +from sqlalchemy import create_engine +from sqlalchemy.exc import OperationalError + +# --------------------------- +# Initialize Flask app +# --------------------------- +app = Flask(__name__) +app.config.from_object(Config) + +# --------------------------- +# Ensure database exists +# --------------------------- +db_uri = app.config['SQLALCHEMY_DATABASE_URI'] +# Extract database name from URI +db_name = db_uri.rsplit('/', 1)[-1] +engine_uri_without_db = db_uri.rsplit('/', 1)[0] + +try: + engine = create_engine(db_uri) + conn = engine.connect() + conn.close() +except OperationalError: + # Database doesn't exist, create it + engine = create_engine(engine_uri_without_db) + conn = engine.connect() + conn.execute(f"CREATE DATABASE {db_name}") + conn.close() + print(f"Database '{db_name}' created successfully.") + +# --------------------------- +# Initialize database +# --------------------------- +db.init_app(app) # initialize db with 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 +# --------------------------- +# Import blueprints AFTER app and db are initialized +from controllers.file_controller import file_bp +app.register_blueprint(file_bp) + +# --------------------------- +# Create database tables if they don't exist +# --------------------------- +with app.app_context(): + db.create_all() + +# --------------------------- +# Run the app +# --------------------------- +if __name__ == '__main__': + app.run(debug=True) diff --git a/flask_file_upload_mvc/config.py b/flask_file_upload_mvc/config.py new file mode 100644 index 0000000..1300c5a --- /dev/null +++ b/flask_file_upload_mvc/config.py @@ -0,0 +1,14 @@ +import os +from dotenv import load_dotenv + +load_dotenv() # Load .env variables + +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 0000000..29e4c7d Binary files /dev/null and b/flask_file_upload_mvc/controllers/__pycache__/file_controller.cpython-314.pyc differ 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..92bb56f --- /dev/null +++ b/flask_file_upload_mvc/controllers/file_controller.py @@ -0,0 +1,81 @@ +import uuid +import logging +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 + +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'] + # Use current_app instead of importing app + folder = f"{current_app.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 + +# --------------------------- +# 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']) + + 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/flask_file_upload_mvc/extensions.py b/flask_file_upload_mvc/extensions.py new file mode 100644 index 0000000..3291920 --- /dev/null +++ b/flask_file_upload_mvc/extensions.py @@ -0,0 +1,4 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() + diff --git a/flask_file_upload_mvc/logs/app.log b/flask_file_upload_mvc/logs/app.log new file mode 100644 index 0000000..6146d68 --- /dev/null +++ b/flask_file_upload_mvc/logs/app.log @@ -0,0 +1,17 @@ +2025-10-19 22:39:28,753 [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 22:39:28,755 [INFO] Press CTRL+C to quit +2025-10-19 22:39:28,759 [INFO] * Restarting with stat +2025-10-19 22:39:29,594 [WARNING] * Debugger is active! +2025-10-19 22:39:29,603 [INFO] * Debugger PIN: 856-154-652 +2025-10-19 22:40:58,654 [INFO] File saved: uploads/dff289d80b884376930c3b830d1f35c2\136890046ad448109786851cb512836a_174836.png +2025-10-19 22:40:58,662 [INFO] 127.0.0.1 - - [19/Oct/2025 22:40:58] "POST /upload HTTP/1.1" 201 - +2025-10-19 22:41:22,007 [INFO] 127.0.0.1 - - [19/Oct/2025 22:41:22] "GET /file/1 HTTP/1.1" 200 - +2025-10-19 22:41:39,067 [INFO] File updated: uploads\70edd663a3bc44f397e74a6c8e0bb250_174836.png +2025-10-19 22:41:39,070 [INFO] 127.0.0.1 - - [19/Oct/2025 22:41:39] "PUT /file/1 HTTP/1.1" 200 - +2025-10-19 22:41:48,250 [INFO] File deleted: uploads\70edd663a3bc44f397e74a6c8e0bb250_174836.png +2025-10-19 22:41:48,251 [INFO] 127.0.0.1 - - [19/Oct/2025 22:41:48] "DELETE /file/1 HTTP/1.1" 200 - +2025-10-19 22:49:57,691 [INFO] * Detected change in 'C:\\Users\\Admin\\source\\repos\\flask_file_upload_mvc\\app.py', reloading +2025-10-19 22:49:57,905 [INFO] * Restarting with stat +2025-10-19 22:50:00,131 [WARNING] * Debugger is active! +2025-10-19 22:50:00,138 [INFO] * Debugger PIN: 856-154-652 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 0000000..67ba8d5 Binary files /dev/null and b/flask_file_upload_mvc/models/__pycache__/uploaded_file.cpython-314.pyc differ 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..0e439d6 --- /dev/null +++ b/flask_file_upload_mvc/models/uploaded_file.py @@ -0,0 +1,7 @@ +from extensions import db # use extensions instead of app + +# 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) diff --git a/flask_file_upload_mvc/requirements.txt b/flask_file_upload_mvc/requirements.txt new file mode 100644 index 0000000..a2fbb49 Binary files /dev/null and b/flask_file_upload_mvc/requirements.txt differ diff --git a/flask_file_upload_mvc/services/__pycache__/file_service.cpython-314.pyc b/flask_file_upload_mvc/services/__pycache__/file_service.cpython-314.pyc new file mode 100644 index 0000000..2dcc969 Binary files /dev/null and b/flask_file_upload_mvc/services/__pycache__/file_service.cpython-314.pyc differ diff --git a/flask_file_upload_mvc/services/file_service.py b/flask_file_upload_mvc/services/file_service.py new file mode 100644 index 0000000..8cdb3c1 --- /dev/null +++ b/flask_file_upload_mvc/services/file_service.py @@ -0,0 +1,78 @@ +import os +import uuid +import logging +from werkzeug.utils import secure_filename +from extensions import db # use extensions instead of app +from models.uploaded_file import UploadedFile + + +# Allowed file extensions +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'pdf', 'txt'} + +def allowed_file(filename): + """Check if file has an allowed extension.""" + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + +def save_file(file, base_folder): + """Save a new file and create a database record.""" + if not allowed_file(file.filename): + raise ValueError('File type not allowed') + + # Ensure folder exists + os.makedirs(base_folder, exist_ok=True) + + # Secure filename and create a unique name + filename = secure_filename(file.filename) + unique_name = f"{uuid.uuid4().hex}_{filename}" + save_path = os.path.join(base_folder, unique_name) + + # Save file to disk + file.save(save_path) + + # Create DB record + 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): + """Update an existing file record with a new file.""" + existing = UploadedFile.query.get(file_id) + if not existing: + raise ValueError('File not found') + + # Remove old file + if os.path.exists(existing.file_path): + os.remove(existing.file_path) + + # Save new file + filename = secure_filename(new_file.filename) + unique_name = f"{uuid.uuid4().hex}_{filename}" + save_path = os.path.join(base_folder, unique_name) + os.makedirs(base_folder, exist_ok=True) + new_file.save(save_path) + + # Update DB record + existing.filename = unique_name + existing.file_path = save_path + db.session.commit() + + logging.info(f"File updated: {save_path}") + return existing + +def delete_file(file_id): + """Delete a file record and remove the file from disk.""" + file_record = UploadedFile.query.get(file_id) + if not file_record: + raise ValueError('File not found') + + # Remove file from disk + if os.path.exists(file_record.file_path): + os.remove(file_record.file_path) + + # Remove DB record + db.session.delete(file_record) + db.session.commit() + logging.info(f"File deleted: {file_record.file_path}")