From 6c994db855a748b4c1d51efa548f1d6c932156f3 Mon Sep 17 00:00:00 2001 From: Cipher Vance Date: Mon, 8 Sep 2025 21:08:40 -0500 Subject: [PATCH 1/6] refactor: change from 3 chars in username to 5 --- services/UserService/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/UserService/user.py b/services/UserService/user.py index b6da4d7..e1a4485 100644 --- a/services/UserService/user.py +++ b/services/UserService/user.py @@ -9,9 +9,9 @@ class UserService: if not username or not password: raise ValueError("Username and password are required") - if len(username) < 3 or len(password) < 8: + if len(username) < 5 or len(password) < 12: raise ValueError( - "Username must be at least 3 characters and password must be at least 8 characters." + "Username must be at least 5 characters and password must be at least 12 characters." ) existing_user = User.query.filter_by(username=username).first() From f396c98cbed4428c1ee78a440cb61951cf54d599 Mon Sep 17 00:00:00 2001 From: Cipher Vance Date: Tue, 9 Sep 2025 08:31:16 -0500 Subject: [PATCH 2/6] chore(docker): include .env, fix port, install dotenv --- .dockerignore | 2 +- Dockerfile | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.dockerignore b/.dockerignore index ce46172..d9b625e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,7 +2,7 @@ __pycache__/ *.py[cod] *.log -.env +!.env venv/ .venv/ dist/ diff --git a/Dockerfile b/Dockerfile index 1468378..c72dff0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,17 +16,17 @@ RUN python -m pip install --upgrade pip && \ pip wheel --no-deps -r requirements.txt -w /wheels && \ pip wheel --no-deps gunicorn -w /wheels - FROM python:3.10-slim AS runtime ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ - PORT=8000 \ + PORT=5000 \ WSGI_MODULE=server:app \ GUNICORN_WORKERS=2 \ GUNICORN_THREADS=4 \ - GUNICORN_TIMEOUT=60 + GUNICORN_TIMEOUT=60 \ + FLASK_APP=server.py WORKDIR /app @@ -35,13 +35,19 @@ RUN groupadd -g 10001 app && useradd -m -u 10001 -g app app COPY --from=builder /wheels /wheels RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels +# Install python-dotenv if not already in requirements.txt +RUN pip install python-dotenv + USER app COPY --chown=app:app . . -EXPOSE 8000 +# Copy .env file specifically +COPY --chown=app:app .env .env + +EXPOSE 5000 HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ - CMD python -c "import os,socket; s=socket.socket(); s.settimeout(2); s.connect(('127.0.0.1', int(os.getenv('PORT', '8000')))); s.close()" + CMD python -c "import os,socket; s=socket.socket(); s.settimeout(2); s.connect(('127.0.0.1', int(os.getenv('PORT', '5000')))); s.close()" CMD ["sh", "-c", "exec gunicorn $WSGI_MODULE --bind=0.0.0.0:$PORT --workers=$GUNICORN_WORKERS --threads=$GUNICORN_THREADS --timeout=$GUNICORN_TIMEOUT --access-logfile=- --error-logfile=- --keep-alive=5"] \ No newline at end of file From 52bb0039809a77ab187a1bf270e45433b79c8fa0 Mon Sep 17 00:00:00 2001 From: Cipher Vance Date: Tue, 9 Sep 2025 08:31:55 -0500 Subject: [PATCH 3/6] feat(models): add email to User and normalize UserProfile --- models/User/user.py | 13 +++++++------ models/UserProfile/user_profile.py | 17 ++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/models/User/user.py b/models/User/user.py index d6f01a4..552796c 100644 --- a/models/User/user.py +++ b/models/User/user.py @@ -8,6 +8,7 @@ class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) + email = db.Column(db.String(120), unique=True, nullable=False) # Add email field _password = db.Column("password", db.String(255), nullable=False) profile = db.relationship('UserProfile', back_populates='user', uselist=False, cascade="all, delete-orphan") @@ -29,11 +30,11 @@ class User(db.Model): @event.listens_for(User, 'after_insert') def create_user_profile(mapper, connection, target): connection.execute( - UserProfile.__table__.insert().values ( - user_id = target.id, - first_name = "", - last_name = "", - bio = "", - profile_picture = "" + UserProfile.__table__.insert().values( + user_id=target.id, + first_name="", + last_name="", + bio="", + profile_picture="" ) ) \ No newline at end of file diff --git a/models/UserProfile/user_profile.py b/models/UserProfile/user_profile.py index 2063b39..d3fa194 100644 --- a/models/UserProfile/user_profile.py +++ b/models/UserProfile/user_profile.py @@ -1,14 +1,13 @@ from models import db class UserProfile(db.Model): - __tablename__ = 'user_profile' + __tablename__ = 'user_profiles' - id = db.Column(db.Integer, primary_key = True) - user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable = False) - first_name = db.Column(db.String(80), nullable = False) - last_name = db.Column(db.String(80), nullable = False) - bio = db.Column(db.Text, nullable = True) - profile_picture = db.Column(db.String(255), nullable = True) + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + first_name = db.Column(db.String(50), nullable=False, default="") + last_name = db.Column(db.String(50), nullable=False, default="") + bio = db.Column(db.Text, default="") + profile_picture = db.Column(db.String(255), default="") - user = db.relationship('User', back_populates='profile') - \ No newline at end of file + user = db.relationship('User', back_populates='profile') \ No newline at end of file From f5f86a2bc737c3ae804e2c495bd35614dd56b658 Mon Sep 17 00:00:00 2001 From: Cipher Vance Date: Tue, 9 Sep 2025 08:32:15 -0500 Subject: [PATCH 4/6] refactor(api): unify auth endpoints under /api, enhance signup --- routes/user_auth/auth.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/routes/user_auth/auth.py b/routes/user_auth/auth.py index 366642f..899d7ba 100644 --- a/routes/user_auth/auth.py +++ b/routes/user_auth/auth.py @@ -1,19 +1,35 @@ from flask import Blueprint, request, jsonify, session from services.UserService.user import UserService -auth_bp = Blueprint("auth", __name__, url_prefix="/auth") +auth_bp = Blueprint("auth", __name__, url_prefix="/api") user_service = UserService() - @auth_bp.route("/signup", methods=["POST"]) def signup(): data = request.get_json() + if not data: + return jsonify({"message": "No data provided"}), 400 + + required_fields = ['username', 'password'] + for field in required_fields: + if not data.get(field): + return jsonify({"message": f"{field} is required"}), 400 + try: - new_user = user_service.create_user(data["username"], data["password"]) - return ( - jsonify({"message": "User created successfully", "username": new_user.username}), - 201, + new_user = user_service.create_user( + username=data["username"], + password=data["password"], + email=data.get("email"), + first_name=data.get("first_name"), + last_name=data.get("last_name") ) + + return jsonify({ + "message": "User created successfully", + "username": new_user.username, + "user_id": new_user.id + }), 201 + except ValueError as e: return jsonify({"message": str(e)}), 400 except Exception as e: @@ -21,15 +37,12 @@ def signup(): print(f"Signup error: {e}") return jsonify({"message": "Internal server error"}), 500 - @auth_bp.route("/login", methods=["POST"]) def login(): data = request.get_json() username = data.get("username") password = data.get("password") - print(f"Login attempt: username={username}, password={password}") - try: user = user_service.verify_user(username, password) session["user_id"] = user.id @@ -41,8 +54,7 @@ def login(): print(f"Login error: {e}") return jsonify({"error": "Internal server error"}), 500 - @auth_bp.route("/logout", methods=["POST"]) def logout(): session.clear() - return jsonify({"message": "Logout successful"}), 200 + return jsonify({"message": "Logout successful"}), 200 \ No newline at end of file From 0698ba5c1f2f428d81da2a508b9f80ba04af7971 Mon Sep 17 00:00:00 2001 From: Cipher Vance Date: Tue, 9 Sep 2025 08:32:49 -0500 Subject: [PATCH 5/6] refactor(service): enrich UserService.create_user --- services/UserService/user.py | 78 ++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/services/UserService/user.py b/services/UserService/user.py index e1a4485..6f1c030 100644 --- a/services/UserService/user.py +++ b/services/UserService/user.py @@ -1,42 +1,60 @@ -from models.User.user import User, db -import logging - -logger = logging.getLogger(__name__) - +from models.User.user import User +from models.UserProfile.user_profile import UserProfile +from models import db +import re class UserService: - def create_user(self, username, password): + def create_user(self, username, password, email=None, first_name=None, last_name=None): if not username or not password: raise ValueError("Username and password are required") - - if len(username) < 5 or len(password) < 12: - raise ValueError( - "Username must be at least 5 characters and password must be at least 12 characters." - ) - - existing_user = User.query.filter_by(username=username).first() + + if email: + email_regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + if not re.match(email_regex, email): + raise ValueError("Invalid email format") + + existing_user = User.query.filter( + (User.username == username) | (User.email == email) + ).first() + if existing_user: - raise ValueError("User already exists") - - new_user = User(username=username, password=password) - db.session.add(new_user) + if existing_user.username == username: + raise ValueError("Username already exists") + else: + raise ValueError("Email already exists") + + if len(password) < 8: + raise ValueError("Password must be at least 8 characters long") + try: + new_user = User( + username=username, + email=email or "", + password=password + ) + + db.session.add(new_user) + db.session.flush() + + user_profile = UserProfile( + user_id=new_user.id, + first_name=first_name or "", + last_name=last_name or "", + bio="", + profile_picture="" + ) + + db.session.add(user_profile) db.session.commit() + + return new_user + except Exception as e: db.session.rollback() - logger.error(f"Error creating user: {e}") - raise ValueError("Could not create user") from e - return new_user - + raise Exception(f"Error creating user: {str(e)}") + def verify_user(self, username, password): user = User.query.filter_by(username=username).first() - if not user: - logger.warning(f"User not found: {username}") + if not user or not user.check_password(password): raise ValueError("Invalid username or password") - - if not user.check_password(password): - logger.warning(f"Invalid password for user: {username}") - raise ValueError("Invalid username or password") - - logger.info(f"User verified: {username}") - return user + return user \ No newline at end of file From ac0b09ad0e8d69dfca4dec6b9b227f7430e16b53 Mon Sep 17 00:00:00 2001 From: Cipher Vance Date: Tue, 9 Sep 2025 08:33:04 -0500 Subject: [PATCH 6/6] feat(db): added migration --- scripts/migrate.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 scripts/migrate.sh diff --git a/scripts/migrate.sh b/scripts/migrate.sh new file mode 100644 index 0000000..405f399 --- /dev/null +++ b/scripts/migrate.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +echo "Running database migrations..." +flask db upgrade + +echo "Starting application..." +exec "$@" \ No newline at end of file