Skip to content

Commit

Permalink
Final changes for issue 02
Browse files Browse the repository at this point in the history
  • Loading branch information
ScotterMonk committed Oct 15, 2024
1 parent e1e6b03 commit 1e84225
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 11 deletions.
6 changes: 5 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from flask import Flask
from .extensions import db, migrate, bcrypt
from .config import Config
from .routes import register_blueprints


def create_app(config_class=Config):
Expand All @@ -13,7 +12,12 @@ def create_app(config_class=Config):
migrate.init_app(app, db)
bcrypt.init_app(app)

# Import models
from .models import User, UserActiveStatusChange

# Register blueprints
from .routes import register_blueprints

register_blueprints(app)

return app
29 changes: 28 additions & 1 deletion app/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
from app.extensions import db, bcrypt
from app.extensions import db
from datetime import datetime, tzinfo, timedelta


class UTC(tzinfo):
def utcoffset(self, dt):
return timedelta(0)

def tzname(self, dt):
return "UTC"

def dst(self, dt):
return timedelta(0)


def utc_now():
return datetime.now(UTC())


class User(db.Model):
__tablename__ = "users"
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)
password = db.Column(db.String(128), nullable=False)
active = db.Column(db.Boolean, default=False) # Issue 02-Active users

def __repr__(self):
return f"<User {self.username}>"


class UserActiveStatusChange(db.Model):
__tablename__ = "users_active_status_changes"
id = db.Column(db.Integer, primary_key=True)
id_user = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
status = db.Column(db.String(10), nullable=False, default="inactive")
date = db.Column(db.DateTime, default=utc_now)
user = db.relationship("User", backref="status_changes")
77 changes: 71 additions & 6 deletions app/routes/user_routes.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,101 @@
from flask import Blueprint, jsonify, request
from ..models import User
from flask import Blueprint, Response, jsonify, request
from ..models import User, UserActiveStatusChange
from ..extensions import db
from ..services.user_service import create_user, check_password
import logging
import sys
import json

# Configure logger to print to shell.
logger = logging.getLogger("alembic.env")
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

user_bp = Blueprint("user_bp", __name__)


# Route to register a new user.
@user_bp.route("/register", methods=["POST"])
def register():
data = request.get_json()
username = data.get("username")
email = data.get("email")
password = data.get("password")

# status = defaults to inactive
user = create_user(username, email, password)
return jsonify({"message": "User registered successfully"}), 201
return jsonify({f"message": "User " + username + " registered successfully"}), 201


# Route to log in a user.
@user_bp.route("/login", methods=["POST"])
def login():
data = request.get_json()
email = data.get("email")
password = data.get("password")

if check_password(email, password) is True:
return jsonify({"message": "Login successful", "email": email}), 200
else:
return jsonify({"message": "Invalid credentials"}), 401


# Dummy profile route for the user.
@user_bp.route("/profile", methods=["GET"])
def profile():
# Dummy profile route for the user
# In a real system, you would have authentication and user session handling
return jsonify({"message": "User profile information"}), 200


# Route to hit to toggle active/inactive status of a user.
@user_bp.route("/toggle-active", methods=["POST"])
def toggle_active():
data = request.get_json()
email = data.get("email")
user = User.query.filter_by(email=email).first()
if user is None:
return jsonify({"message": "User not found"}), 404
else:
user.active = not user.active
# Save the status change to UserActiveStatusChange table.
status_change = UserActiveStatusChange(
id_user=user.id, status="active" if user.active else "inactive"
)
db.session.commit()
return jsonify({"message": f"User status toggled to {user.active}"}), 200


# Route to show all users.
@user_bp.route("/users", methods=["GET"])
def users():
users = User.query.all()
user_list = []
for user in users:
user_list.append(
{"username": user.username, "email": user.email, "active": user.active}
)
logger.debug(f"{user.username} | {user.email} | {user.active}")
response = json.dumps(user_list)
return Response(response, mimetype="application/json"), 200


# Route to delete a user.
@user_bp.route("/delete-user", methods=["POST"])
def delete_user():
data = request.get_json()
email = data.get("email")
user = User.query.filter_by(email=email).first()
if user is None:
return jsonify({"message": "User not found"}), 404
else:
# Delete references to user in UserActiveStatusChange table.
status_changes = UserActiveStatusChange.query.filter_by(id_user=user.id).all()
for status_change in status_changes:
db.session.delete(status_change)
# Now delete the user.
db.session.delete(user)
db.session.commit()
logger.debug(f"{user} deleted")
return jsonify({"message": "User deleted"}), 200
8 changes: 7 additions & 1 deletion app/services/user_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ..models import User
from ..models import User, UserActiveStatusChange
from ..extensions import db, bcrypt


Expand All @@ -25,3 +25,9 @@ def check_password(email, password):
return True
else:
return False


def log_status_change(user_id, new_status):
status_change = UserActiveStatusChange(id_user=user_id, status=new_status)
db.session.add(status_change)
db.session.commit()
25 changes: 25 additions & 0 deletions migrations/versions/522fbb921d60_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""empty message
Revision ID: 522fbb921d60
Revises: 19c3d8fa2068, f9213fe72dcc
Create Date: 2024-10-15 07:12:23.368660
"""

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "522fbb921d60"
down_revision = ("19c3d8fa2068", "f9213fe72dcc")
branch_labels = None
depends_on = None


def upgrade():
pass


def downgrade():
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Update User and UserActiveStatusChange models
Revision ID: d7cc877e7cf0
Revises: 522fbb921d60
Create Date: 2024-10-15 08:57:26.306876
"""

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "d7cc877e7cf0"
down_revision = "522fbb921d60"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"users",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("username", sa.String(length=80), nullable=False),
sa.Column("email", sa.String(length=120), nullable=False),
sa.Column("password", sa.String(length=128), nullable=False),
sa.Column("active", sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("email"),
sa.UniqueConstraint("username"),
)
op.create_table(
"users_active_status_changes",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("id_user", sa.Integer(), nullable=False),
sa.Column("status", sa.Boolean(), nullable=False),
sa.Column("date", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["id_user"],
["users.id"],
),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("users_active_status_changes")
op.drop_table("users")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Change status column from boolean to string
Revision ID: dcfa167aa33c
Revises: d7cc877e7cf0
Create Date: 2024-10-15 09:32:20.240064
"""

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "dcfa167aa33c"
down_revision = "d7cc877e7cf0"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("users_active_status_changes", schema=None) as batch_op:
batch_op.alter_column(
"status",
existing_type=sa.BOOLEAN(),
type_=sa.String(length=10),
existing_nullable=False,
)

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("users_active_status_changes", schema=None) as batch_op:
batch_op.alter_column(
"status",
existing_type=sa.String(length=10),
type_=sa.BOOLEAN(),
existing_nullable=False,
)

# ### end Alembic commands ###
17 changes: 15 additions & 2 deletions run.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import os
from app import create_app
from app import create_app, db
from app.config import DevelopmentConfig, ProductionConfig

# Set up the app
config_class = (
DevelopmentConfig if os.getenv("FLASK_ENV") == "development" else ProductionConfig
)
app = create_app(config_class=config_class)

# Import models after the app is created
from app.models import User, UserActiveStatusChange

if __name__ == "__main__":
app.run()
with app.app_context():
print("Registered tables:")
for table in db.metadata.tables:
print(f"- {table}")

# print("\nAttempting to create tables...")
# db.create_all()
# print("Tables created successfully.")

app.run(debug=True)

0 comments on commit 1e84225

Please sign in to comment.