Compare commits
No commits in common. "db6f26df5d9ac8f1a3b94dc7b591634318065a0b" and "a8bcd5e249bf9552dd433d5ec26937f19e861282" have entirely different histories.
db6f26df5d
...
a8bcd5e249
7 changed files with 62 additions and 106 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*.log
|
*.log
|
||||||
!.env
|
.env
|
||||||
venv/
|
venv/
|
||||||
.venv/
|
.venv/
|
||||||
dist/
|
dist/
|
||||||
|
|
|
||||||
16
Dockerfile
16
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 -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"]
|
||||||
|
|
@ -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 = ""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -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')
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Running database migrations..."
|
|
||||||
flask db upgrade
|
|
||||||
|
|
||||||
echo "Starting application..."
|
|
||||||
exec "$@"
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue