diff --git a/.dockerignore b/.dockerignore index d9b625e..ce46172 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 c72dff0..1468378 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=5000 \ + PORT=8000 \ WSGI_MODULE=server:app \ GUNICORN_WORKERS=2 \ GUNICORN_THREADS=4 \ - GUNICORN_TIMEOUT=60 \ - FLASK_APP=server.py + GUNICORN_TIMEOUT=60 WORKDIR /app @@ -35,19 +35,13 @@ 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 . . -# Copy .env file specifically -COPY --chown=app:app .env .env - -EXPOSE 5000 +EXPOSE 8000 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', '5000')))); s.close()" + 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 ["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 diff --git a/models/User/user.py b/models/User/user.py index 552796c..d6f01a4 100644 --- a/models/User/user.py +++ b/models/User/user.py @@ -8,7 +8,6 @@ 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") @@ -30,11 +29,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 d3fa194..2063b39 100644 --- a/models/UserProfile/user_profile.py +++ b/models/UserProfile/user_profile.py @@ -1,13 +1,14 @@ from models import db class UserProfile(db.Model): - __tablename__ = 'user_profiles' + __tablename__ = 'user_profile' - 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="") + 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) - 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 diff --git a/routes/user_auth/auth.py b/routes/user_auth/auth.py index 899d7ba..366642f 100644 --- a/routes/user_auth/auth.py +++ b/routes/user_auth/auth.py @@ -1,35 +1,19 @@ from flask import Blueprint, request, jsonify, session from services.UserService.user import UserService -auth_bp = Blueprint("auth", __name__, url_prefix="/api") +auth_bp = Blueprint("auth", __name__, url_prefix="/auth") 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( - username=data["username"], - password=data["password"], - email=data.get("email"), - first_name=data.get("first_name"), - last_name=data.get("last_name") + new_user = user_service.create_user(data["username"], data["password"]) + return ( + jsonify({"message": "User created successfully", "username": new_user.username}), + 201, ) - - 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: @@ -37,12 +21,15 @@ 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 @@ -54,7 +41,8 @@ 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 \ No newline at end of file + return jsonify({"message": "Logout successful"}), 200 diff --git a/scripts/migrate.sh b/scripts/migrate.sh deleted file mode 100644 index 405f399..0000000 --- a/scripts/migrate.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -e - -echo "Running database migrations..." -flask db upgrade - -echo "Starting application..." -exec "$@" \ No newline at end of file diff --git a/services/UserService/user.py b/services/UserService/user.py index 6f1c030..b6da4d7 100644 --- a/services/UserService/user.py +++ b/services/UserService/user.py @@ -1,60 +1,42 @@ -from models.User.user import User -from models.UserProfile.user_profile import UserProfile -from models import db -import re +from models.User.user import User, db +import logging + +logger = logging.getLogger(__name__) + class UserService: - def create_user(self, username, password, email=None, first_name=None, last_name=None): + def create_user(self, username, password): if not username or not password: raise ValueError("Username and password are required") - - 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 len(username) < 3 or len(password) < 8: + raise ValueError( + "Username must be at least 3 characters and password must be at least 8 characters." + ) + + existing_user = User.query.filter_by(username=username).first() if existing_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") - + raise ValueError("User already exists") + + new_user = User(username=username, password=password) + db.session.add(new_user) 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() - raise Exception(f"Error creating user: {str(e)}") - + logger.error(f"Error creating user: {e}") + raise ValueError("Could not create user") from e + return new_user + def verify_user(self, username, password): user = User.query.filter_by(username=username).first() - if not user or not user.check_password(password): + if not user: + logger.warning(f"User not found: {username}") raise ValueError("Invalid username or password") - return user \ No newline at end of file + + 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