diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..128b3a6 --- /dev/null +++ b/backend/.env @@ -0,0 +1,4 @@ +SECRET_KEY=MVPhXY*yNXPE2MbXDdpxXk +REDIS_PASSWORD=eYVX7EwVmmxKPCDmwMtyKVge8oLd2t81 +POSTGRESQL_DATABASE_URL='postgresql+psycopg2://arufa:Plop2099@localhost:5432/arufalab' +# POSTGRESQL_DATABASE_URL => 'postgresql+psycopg2://arufa:password@host:port/database' \ No newline at end of file diff --git a/backend/__pycache__/config.cpython-312.pyc b/backend/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000..30cb937 Binary files /dev/null and b/backend/__pycache__/config.cpython-312.pyc differ diff --git a/backend/__pycache__/models.cpython-312.pyc b/backend/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..cbe0aa1 Binary files /dev/null and b/backend/__pycache__/models.cpython-312.pyc differ diff --git a/backend/config.py b/backend/config.py index aaf4a7d..c1ec7e4 100644 --- a/backend/config.py +++ b/backend/config.py @@ -21,8 +21,16 @@ app.config["SESSION_TYPE"] = 'redis' app.config["SESSION_PERMANENT"] = False app.config["SESSION_USE_SIGNER"] = True app.config["SESSION_REDIS"] = redis.from_url("redis://192.168.50.107:6379") +# upload directory +app.config["UPLOAD_DIRECTORY"] = 'uploads/' # app.config["REDIS_PASSWORD"] = os.environ["REDIS_PASSWORD"] +# File value limit +app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 # 16MB + +# List of allowed file extensions +app.config['ALLOWED_EXTENSIONS'] = ['.jpg', '.jpeg', '.png', '.gif'] + db = SQLAlchemy(app) server_session = Session(app) diff --git a/backend/instance/mydatabase.db b/backend/instance/mydatabase.db new file mode 100644 index 0000000..5c49581 Binary files /dev/null and b/backend/instance/mydatabase.db differ diff --git a/backend/main.py b/backend/main.py index 73c4320..f263ea1 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,16 +1,45 @@ -from flask import Flask, render_template, request +from flask import Flask, render_template, request, redirect, send_from_directory +from config import app, db, bcrypt +from models import Contact, User +from werkzeug.utils import secure_filename +from werkzeug.exceptions import RequestEntityTooLarge +import os @app.route('/') def index(): - return render_template('index.html') - -@app.route('/api/upload', methods=['POST']) -def upload(): - file = request.files['file'] + files = os.listdir(app.config['UPLOAD_DIRECTORY']) + images = [] - # save the file - file.save(f'uploads/{file.filename}') + for file in files: + if os.path.splitext(file)[1].lower() in app.config['ALLOWED_EXTENSIONS']: + images.append(file) + return render_template('index.html', images=images) + +@app.route('/upload', methods=["POST"]) +def upload(): + try: + file = request.files['file'] + file_extension = os.path.splitext(file.filename)[1] # [0] is the filename + + # save the file if it exists + if file: + if file_extension not in app.config['ALLOWED_EXTENSIONS']: + return 'File is not an image' + file.save(os.path.join( + app.config['UPLOAD_DIRECTORY'], + secure_filename(file.filename) + )) + except RequestEntityTooLarge: # except Exception would catch all errors instead of specified + return 'File is larger than the limit of 16mb.' + + return redirect('/') + +# Handle Images +@app.route('/serve-image/', methods=['GET']) +def serve_image(filename): + return send_from_directory(app.config['UPLOAD_DIRECTORY'], filename) + # Main if __name__ == "__main__": diff --git a/backend/models.py b/backend/models.py new file mode 100644 index 0000000..0bc5b6f --- /dev/null +++ b/backend/models.py @@ -0,0 +1,34 @@ +from config import db +from uuid import uuid4 + +class Contact(db.Model): + id = db.Column(db.Integer, primary_key=True) + first_name = db.Column(db.String(80), unique=False, nullable=False) + last_name = db.Column(db.String(80), unique=False, nullable=False) + email = db.Column(db.String(120), unique=False, nullable=False) + + def to_json(self): + return { + "id": self.id, + "firstName":self.first_name, + "lastName":self.last_name, + "email": self.email, + } + + +def get_uuid(): + return uuid4().hex + +class User(db.Model): + __tablename__ = "users" + id = db.Column(db.String(32), primary_key=True, unique=True, default=get_uuid) + email = db.Column(db.String(345), unique=True) + password = db.Column(db.Text, nullable=False) + +# class Category(db.Model): +# __tablename__ = "categories" +# id = db.Column(db.String(32), primary_key=True, unique=True, default=get_uuid) +# name = db.Column(db.String(345), default='New Shirt') +# number = db.Column(db.Integer) +# sale = db.Column(db.Boolean, default=False) + \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index ed950a5..352b66f 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,4 +4,7 @@ flask-bcrypt flask-cors python-dotenv flask-session -redis \ No newline at end of file +redis +Flask-Migrate +flask_validator +psycopg2-binary \ No newline at end of file diff --git a/backend/static/styles.css b/backend/static/styles.css new file mode 100644 index 0000000..c8da789 --- /dev/null +++ b/backend/static/styles.css @@ -0,0 +1,130 @@ +:root { + --black: #000000; + --almost-black: #080708; + --blue: #3772FF; + --blue-light: #3787ff; + --red: #DF2935; + --yellow: #FDCA40; + --light: #E6E8E6; + --white: #FFFFFF; + } + + * { + position: relative; + box-sizing: border-box; + } + + body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 22px; + line-height: 1.5; + color: var(--white); + background: var(--black); + background-image: linear-gradient(to bottom right, var(--almost-black), var(--black)); + } + + .app { + height: 100vh; + display: flex; + } + + .form-section { + padding: 15px 0 0 15px; + width: 25%; + color: var(--light); + flex-shrink: 0; + } + + .images-section { + padding: 15px 0 0 15px; + width: 75%; + height: 100vh; + align-content: flex-start; + flex-wrap: wrap; + display: flex; + overflow-y: auto; + } + + .images-section a { + margin: 0 15px 15px 0; + padding-bottom: 20%; + content: ''; + width: calc(33.33% - 15px); + height: 0; + display: block; + } + + .images-section img { + position: absolute; + top: 0; + width: 100%; + height: 100%; + border-radius: 4px; + object-fit: cover; + display: block; + } + + @media screen and (max-width: 1200px) { + + .app { + flex-direction: column; + } + + .form-section { + padding-right: 15px; + padding-bottom: 15px; + width: 100%; + } + + .images-section { + padding-top: 0; + width: 100%; + height: auto; + } + } + + @media screen and (max-width: 800px) { + + .images-section a { + padding-bottom: 60%; + width: calc(100% - 15px); + } + } + + /* Forms */ + form { + width: 100%; + flex-direction: column; + display: flex; + } + + input[type='file'] { + margin: 0 0 12px; + padding: 10px 10px; + border-radius: 4px; + font-size: 22px; + color: var(--almost-black); + background: var(--white); + } + + input[type='submit'], + button[type='button'] { + padding: 13px 20px; + font-size: 22px; + font-weight: bold; + color: var(--white); + border: none; + border-radius: 4px; + outline: none; + box-shadow: none; + cursor: pointer; + background: var(--blue); + background-image: linear-gradient(to bottom, var(--blue-light), var(--blue)); + transition: box-shadow .2s ease; + } + + input[type='submit']:hover, + button[type='button']:hover { + box-shadow: inset 0 -25px 25px var(--blue-light); + } \ No newline at end of file diff --git a/backend/templates/index.html b/backend/templates/index.html index 9e73d78..a5bc31d 100644 --- a/backend/templates/index.html +++ b/backend/templates/index.html @@ -11,20 +11,20 @@
-
- - - -
- +
+ + +
- - - + {% for image in images %} + + + + {% endfor %}