Compare commits

..

No commits in common. "db6f26df5d9ac8f1a3b94dc7b591634318065a0b" and "a8bcd5e249bf9552dd433d5ec26937f19e861282" have entirely different histories.

7 changed files with 62 additions and 106 deletions

View file

@ -2,7 +2,7 @@
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*.log *.log
!.env .env
venv/ venv/
.venv/ .venv/
dist/ dist/

View file

@ -16,17 +16,17 @@ RUN python -m pip install --upgrade pip && \
pip wheel --no-deps -r requirements.txt -w /wheels && \ pip wheel --no-deps -r requirements.txt -w /wheels && \
pip wheel --no-deps gunicorn -w /wheels pip wheel --no-deps gunicorn -w /wheels
FROM python:3.10-slim AS runtime FROM python:3.10-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \ PIP_NO_CACHE_DIR=1 \
PORT=5000 \ PORT=8000 \
WSGI_MODULE=server:app \ WSGI_MODULE=server:app \
GUNICORN_WORKERS=2 \ GUNICORN_WORKERS=2 \
GUNICORN_THREADS=4 \ GUNICORN_THREADS=4 \
GUNICORN_TIMEOUT=60 \ GUNICORN_TIMEOUT=60
FLASK_APP=server.py
WORKDIR /app WORKDIR /app
@ -35,19 +35,13 @@ RUN groupadd -g 10001 app && useradd -m -u 10001 -g app app
COPY --from=builder /wheels /wheels COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /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 USER app
COPY --chown=app:app . . COPY --chown=app:app . .
# Copy .env file specifically EXPOSE 8000
COPY --chown=app:app .env .env
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ 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"] 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"]

View file

@ -8,7 +8,6 @@ class User(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False) 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) _password = db.Column("password", db.String(255), nullable=False)
profile = db.relationship('UserProfile', back_populates='user', uselist=False, cascade="all, delete-orphan") 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') @event.listens_for(User, 'after_insert')
def create_user_profile(mapper, connection, target): def create_user_profile(mapper, connection, target):
connection.execute( connection.execute(
UserProfile.__table__.insert().values( UserProfile.__table__.insert().values (
user_id=target.id, user_id = target.id,
first_name="", first_name = "",
last_name="", last_name = "",
bio="", bio = "",
profile_picture="" profile_picture = ""
) )
) )

View file

@ -1,13 +1,14 @@
from models import db from models import db
class UserProfile(db.Model): class UserProfile(db.Model):
__tablename__ = 'user_profiles' __tablename__ = 'user_profile'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key = True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable = False)
first_name = db.Column(db.String(50), nullable=False, default="") first_name = db.Column(db.String(80), nullable = False)
last_name = db.Column(db.String(50), nullable=False, default="") last_name = db.Column(db.String(80), nullable = False)
bio = db.Column(db.Text, default="") bio = db.Column(db.Text, nullable = True)
profile_picture = db.Column(db.String(255), default="") profile_picture = db.Column(db.String(255), nullable = True)
user = db.relationship('User', back_populates='profile') user = db.relationship('User', back_populates='profile')

View file

@ -1,35 +1,19 @@
from flask import Blueprint, request, jsonify, session from flask import Blueprint, request, jsonify, session
from services.UserService.user import UserService 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() user_service = UserService()
@auth_bp.route("/signup", methods=["POST"]) @auth_bp.route("/signup", methods=["POST"])
def signup(): def signup():
data = request.get_json() 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: try:
new_user = user_service.create_user( new_user = user_service.create_user(data["username"], data["password"])
username=data["username"], return (
password=data["password"], jsonify({"message": "User created successfully", "username": new_user.username}),
email=data.get("email"), 201,
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: except ValueError as e:
return jsonify({"message": str(e)}), 400 return jsonify({"message": str(e)}), 400
except Exception as e: except Exception as e:
@ -37,12 +21,15 @@ def signup():
print(f"Signup error: {e}") print(f"Signup error: {e}")
return jsonify({"message": "Internal server error"}), 500 return jsonify({"message": "Internal server error"}), 500
@auth_bp.route("/login", methods=["POST"]) @auth_bp.route("/login", methods=["POST"])
def login(): def login():
data = request.get_json() data = request.get_json()
username = data.get("username") username = data.get("username")
password = data.get("password") password = data.get("password")
print(f"Login attempt: username={username}, password={password}") print(f"Login attempt: username={username}, password={password}")
try: try:
user = user_service.verify_user(username, password) user = user_service.verify_user(username, password)
session["user_id"] = user.id session["user_id"] = user.id
@ -54,7 +41,8 @@ def login():
print(f"Login error: {e}") print(f"Login error: {e}")
return jsonify({"error": "Internal server error"}), 500 return jsonify({"error": "Internal server error"}), 500
@auth_bp.route("/logout", methods=["POST"]) @auth_bp.route("/logout", methods=["POST"])
def logout(): def logout():
session.clear() session.clear()
return jsonify({"message": "Logout successful"}), 200 return jsonify({"message": "Logout successful"}), 200

View file

@ -1,8 +0,0 @@
#!/bin/bash
set -e
echo "Running database migrations..."
flask db upgrade
echo "Starting application..."
exec "$@"

View file

@ -1,60 +1,42 @@
from models.User.user import User from models.User.user import User, db
from models.UserProfile.user_profile import UserProfile import logging
from models import db
import re logger = logging.getLogger(__name__)
class UserService: 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: if not username or not password:
raise ValueError("Username and password are required") raise ValueError("Username and password are required")
if email: if len(username) < 3 or len(password) < 8:
email_regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' raise ValueError(
if not re.match(email_regex, email): "Username must be at least 3 characters and password must be at least 8 characters."
raise ValueError("Invalid email format") )
existing_user = User.query.filter( existing_user = User.query.filter_by(username=username).first()
(User.username == username) | (User.email == email)
).first()
if existing_user: if existing_user:
if existing_user.username == username: raise ValueError("User already exists")
raise ValueError("Username already exists")
else: new_user = User(username=username, password=password)
raise ValueError("Email already exists") db.session.add(new_user)
if len(password) < 8:
raise ValueError("Password must be at least 8 characters long")
try: 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() db.session.commit()
return new_user
except Exception as e: except Exception as e:
db.session.rollback() 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): def verify_user(self, username, password):
user = User.query.filter_by(username=username).first() 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") raise ValueError("Invalid username or password")
return user
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