Compare commits
No commits in common. "22c080572e173fdd525ad0a25c288f4e2c575b78" and "f3e428bd0e683519d5cd3b5edac4cd8464871636" have entirely different histories.
22c080572e
...
f3e428bd0e
5 changed files with 39 additions and 161 deletions
78
README.md
78
README.md
|
@ -1,68 +1,14 @@
|
||||||
### Flask-API managing Users And Events
|
|
||||||
|
|
||||||
A RESTful API that manages events, offering users the ability to schedule, retrieve,
|
|
||||||
update, delete, and be reminded of events with additional advanced features.
|
|
||||||
|
|
||||||
# Flask-API - Interface
|
|
||||||
- The User Will be able to retrieve upcomming event/s
|
|
||||||
- sort by
|
|
||||||
- popularity
|
|
||||||
- date
|
|
||||||
- filter by:
|
|
||||||
- location
|
|
||||||
- The User will be abale to create/update a new event
|
|
||||||
- The User will be able to login/out
|
|
||||||
- The User can `Attend`/`UnAttend` to an event
|
|
||||||
- The Anonymous User will be able to create a new user
|
|
||||||
|
|
||||||
- Event:
|
|
||||||
- (Auth Users) An event is an upcomming Party/Concert/Sport event/etc. It is tied to a user that created this event.
|
|
||||||
|
|
||||||
- Authentication:
|
|
||||||
- The User will be able to login:
|
|
||||||
- The API will generate a JWT token and set the userId inside the Flask session.
|
|
||||||
- The API will return the JWT token in the response cookie header.
|
|
||||||
|
|
||||||
# Backend-reminder
|
|
||||||
- The Users will be able to be reminded of upcoming events.
|
|
||||||
- The backend will Send reminders 30 minutes before the event's scheduled time.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
- Sqlite(for simplicity use)
|
|
||||||
# Schema
|
|
||||||
- User -
|
|
||||||
- Id (UUID, primary key)
|
|
||||||
- Name (string, required)
|
|
||||||
- Email (string, required, unique)
|
|
||||||
- Password_hash (string, required)
|
|
||||||
- location (string, required)
|
|
||||||
- Event
|
|
||||||
- Id (int, primary key)
|
|
||||||
- Title (string, required)
|
|
||||||
- Description (string, required)
|
|
||||||
- Location (string, required)
|
|
||||||
- Deleted (bool, required)
|
|
||||||
- DueDate (string, required)
|
|
||||||
- User_id (string, required, foreign key)
|
|
||||||
|
|
||||||
- User Event Association (Many to Many)
|
|
||||||
- User_id (string, required, foreign key)
|
|
||||||
- Event_id (Integer, required, foreign key)
|
|
||||||
- Notification Made (bool, required)
|
|
||||||
|
|
||||||
|
|
||||||
# API EndPoints
|
|
||||||
- GET /events (Optional: ?['location' = String, 'sort_by' = Enum('date'/'popularity'/'creation' ) ])
|
|
||||||
- GET /events/{id} - returns a single event
|
|
||||||
- POST /events - create a new event (Auth)
|
|
||||||
- PUT /events/{id} - update an event (Auth + authorized)
|
|
||||||
- DELETE /events/{id} - (Soft)delete an event (Auth + authorized)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
To create the DB in sqlite, make those commends:
|
||||||
|
```
|
||||||
|
flask db init
|
||||||
|
flask db migrate -m "initial migration"
|
||||||
|
flask db upgrade
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```
|
||||||
|
python3 -m flask db init
|
||||||
|
python3 -m flask db migrate -m "initial migration"
|
||||||
|
python3 -m flask db upgrade
|
||||||
|
```
|
|
@ -1,64 +0,0 @@
|
||||||
"""nullable=False to event and user tables
|
|
||||||
|
|
||||||
Revision ID: 902a6851ae27
|
|
||||||
Revises: bb3a88007475
|
|
||||||
Create Date: 2024-01-08 15:26:14.845553
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '902a6851ae27'
|
|
||||||
down_revision = 'bb3a88007475'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('event', schema=None) as batch_op:
|
|
||||||
batch_op.alter_column('description',
|
|
||||||
existing_type=sa.VARCHAR(length=200),
|
|
||||||
nullable=False)
|
|
||||||
batch_op.alter_column('location',
|
|
||||||
existing_type=sa.VARCHAR(length=100),
|
|
||||||
nullable=False)
|
|
||||||
batch_op.alter_column('duedate',
|
|
||||||
existing_type=sa.DATETIME(),
|
|
||||||
nullable=False)
|
|
||||||
|
|
||||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
|
||||||
batch_op.alter_column('name',
|
|
||||||
existing_type=sa.VARCHAR(length=100),
|
|
||||||
nullable=False)
|
|
||||||
batch_op.alter_column('password_hash',
|
|
||||||
existing_type=sa.VARCHAR(length=128),
|
|
||||||
nullable=False)
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
|
||||||
batch_op.alter_column('password_hash',
|
|
||||||
existing_type=sa.VARCHAR(length=128),
|
|
||||||
nullable=True)
|
|
||||||
batch_op.alter_column('name',
|
|
||||||
existing_type=sa.VARCHAR(length=100),
|
|
||||||
nullable=True)
|
|
||||||
|
|
||||||
with op.batch_alter_table('event', schema=None) as batch_op:
|
|
||||||
batch_op.alter_column('duedate',
|
|
||||||
existing_type=sa.DATETIME(),
|
|
||||||
nullable=True)
|
|
||||||
batch_op.alter_column('location',
|
|
||||||
existing_type=sa.VARCHAR(length=100),
|
|
||||||
nullable=True)
|
|
||||||
batch_op.alter_column('description',
|
|
||||||
existing_type=sa.VARCHAR(length=200),
|
|
||||||
nullable=True)
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
10
models.py
10
models.py
|
@ -15,10 +15,10 @@ user_event_association = Table('user_event', db.Model.metadata,
|
||||||
class Event(db.Model):
|
class Event(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
title = db.Column(db.String(100), nullable=False)
|
title = db.Column(db.String(100), nullable=False)
|
||||||
description = db.Column(db.String(200), nullable=False)
|
description = db.Column(db.String(200))
|
||||||
location = db.Column(db.String(100), nullable=False)
|
location = db.Column(db.String(100))
|
||||||
deleted = db.Column(db.Boolean, default=False)
|
deleted = db.Column(db.Boolean, default=False)
|
||||||
duedate = db.Column(db.DateTime, nullable=False)
|
duedate = db.Column(db.DateTime)
|
||||||
created_at = db.Column(db.DateTime, default=db.func.now())
|
created_at = db.Column(db.DateTime, default=db.func.now())
|
||||||
user_id = db.Column(db.String(36), db.ForeignKey('user.id'), nullable=False)
|
user_id = db.Column(db.String(36), db.ForeignKey('user.id'), nullable=False)
|
||||||
users = db.relationship('User', secondary=user_event_association, back_populates='events')
|
users = db.relationship('User', secondary=user_event_association, back_populates='events')
|
||||||
|
@ -37,9 +37,9 @@ class Event(db.Model):
|
||||||
|
|
||||||
class User(db.Model):
|
class User(db.Model):
|
||||||
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||||
name = db.Column(db.String(100), nullable=False)
|
name = db.Column(db.String(100))
|
||||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
password_hash = db.Column(db.String(128), nullable=False)
|
password_hash = db.Column(db.String(128))
|
||||||
location = db.Column(db.String(100), nullable=False)
|
location = db.Column(db.String(100), nullable=False)
|
||||||
events = db.relationship('Event', secondary=user_event_association, back_populates='users')
|
events = db.relationship('Event', secondary=user_event_association, back_populates='users')
|
||||||
|
|
||||||
|
|
|
@ -22,17 +22,18 @@ def create_event():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
# Get All Events
|
||||||
@eventRoutes.route('/', methods=['GET'])
|
@eventRoutes.route('/', methods=['GET'])
|
||||||
@eventRoutes.route('', methods=['GET'])
|
@eventRoutes.route('', methods=['GET'])
|
||||||
|
@authenticate_user
|
||||||
def get_events():
|
def get_events():
|
||||||
try:
|
try:
|
||||||
location = request.args.get('location')
|
user_events = EventService.get_upcoming_events(g.user_id)
|
||||||
sort_by = request.args.get('sort_by')
|
|
||||||
user_events = EventService.get_upcoming_events(location, sort_by)
|
|
||||||
return {"events": user_events, "count": len(user_events)}, 200
|
return {"events": user_events, "count": len(user_events)}, 200
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": str(e)}, 500
|
return {"error": str(e)}, 500
|
||||||
|
|
||||||
|
# Get Event by ID
|
||||||
@eventRoutes.route('/<int:event_id>', methods=['GET'])
|
@eventRoutes.route('/<int:event_id>', methods=['GET'])
|
||||||
@authenticate_user
|
@authenticate_user
|
||||||
def get_event(event_id):
|
def get_event(event_id):
|
||||||
|
@ -41,6 +42,7 @@ def get_event(event_id):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": str(e)}, 500
|
return {"error": str(e)}, 500
|
||||||
|
|
||||||
|
# Update Event
|
||||||
@eventRoutes.route('/<int:event_id>', methods=['PUT'])
|
@eventRoutes.route('/<int:event_id>', methods=['PUT'])
|
||||||
@validate_event_post_request
|
@validate_event_post_request
|
||||||
@authenticate_user
|
@authenticate_user
|
||||||
|
@ -55,6 +57,7 @@ def update_event(event_id):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
# DELETE Event
|
||||||
@eventRoutes.route('/<int:event_id>', methods=['DELETE'])
|
@eventRoutes.route('/<int:event_id>', methods=['DELETE'])
|
||||||
@authenticate_user
|
@authenticate_user
|
||||||
def delete_event(event_id):
|
def delete_event(event_id):
|
||||||
|
@ -68,20 +71,24 @@ def delete_event(event_id):
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
# User atended to event with id event_id and user must be loggedin
|
||||||
@eventRoutes.route('/<int:event_id>/attend', methods=['POST'])
|
@eventRoutes.route('/<int:event_id>/attend', methods=['POST'])
|
||||||
@authenticate_user
|
@authenticate_user
|
||||||
def attend_event(event_id):
|
def attend_event(event_id):
|
||||||
response = EventService.attend_event(event_id)
|
response = EventService.attend_event(event_id)
|
||||||
|
# Check if there's an error in the response
|
||||||
if 'error' in response:
|
if 'error' in response:
|
||||||
return jsonify({'error': response['error']}), 400
|
return jsonify({'error': response['error']}), 400 # Or another appropriate status code
|
||||||
|
|
||||||
|
# If no error, return success response
|
||||||
return jsonify({'message': response['message']}), 200
|
return jsonify({'message': response['message']}), 200
|
||||||
|
|
||||||
@eventRoutes.route('/<int:event_id>/unattend', methods=['POST'])
|
@eventRoutes.route('/<int:event_id>/unattend', methods=['POST'])
|
||||||
@authenticate_user
|
@authenticate_user
|
||||||
def unattend_event(event_id):
|
def unattend_event(event_id):
|
||||||
response = EventService.unattend_event(event_id)
|
response = EventService.unattend_event(event_id)
|
||||||
|
# Check if there's an error in the response
|
||||||
if 'error' in response:
|
if 'error' in response:
|
||||||
return jsonify({'error': response['error']}), 400
|
return jsonify({'error': response['error']}), 400 # Or another appropriate status code
|
||||||
|
|
||||||
return jsonify({'message': response['message']}), 200
|
return jsonify({'message': response['message']}), 200
|
|
@ -3,7 +3,6 @@ from models import db, Event, user_event_association
|
||||||
from services.UserService import UserService
|
from services.UserService import UserService
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import g
|
from flask import g
|
||||||
from sqlalchemy import func
|
|
||||||
|
|
||||||
class EventService:
|
class EventService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -20,23 +19,12 @@ class EventService:
|
||||||
return new_event
|
return new_event
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_upcoming_events(location=None, sort_by=None):
|
def get_upcoming_events(user_id):
|
||||||
query = Event.query.filter(
|
events=Event.query.filter_by(deleted=False).all()
|
||||||
Event.deleted == False,
|
if(events):
|
||||||
Event.duedate > datetime.now()
|
return [event.to_dict() for event in events]
|
||||||
)
|
else:
|
||||||
if location:
|
return []
|
||||||
query = query.filter(Event.location.ilike(f"%{location}%"))
|
|
||||||
|
|
||||||
if sort_by == 'date':
|
|
||||||
query = query.order_by(Event.duedate)
|
|
||||||
elif sort_by == 'popularity':
|
|
||||||
query = query.outerjoin(user_event_association).group_by(Event.id).order_by(func.count(user_event_association.c.user_id).desc())
|
|
||||||
elif sort_by == 'creation':
|
|
||||||
query = query.order_by(Event.created_at)
|
|
||||||
|
|
||||||
events = query.all()
|
|
||||||
return [event.to_dict() for event in events]
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_event_by_id(event_id, user_id):
|
def get_event_by_id(event_id, user_id):
|
||||||
|
@ -48,7 +36,7 @@ class EventService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_event(event_id, data):
|
def update_event(event_id, data):
|
||||||
event = Event.query.filter_by(user_id=g.user_id, id=event_id).first()
|
event = Event.query.get(event_id)
|
||||||
if not event:
|
if not event:
|
||||||
return None
|
return None
|
||||||
event.title = data['title']
|
event.title = data['title']
|
||||||
|
@ -60,7 +48,7 @@ class EventService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_event(event_id):
|
def delete_event(event_id):
|
||||||
event = Event.query.filter_by(id=event_id, user_id=g.user_id, deleted=False).first()
|
event = Event.query.filter_by(id=event_id, deleted=False).first()
|
||||||
if event:
|
if event:
|
||||||
event.deleted = True
|
event.deleted = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -71,7 +59,8 @@ class EventService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def attend_event(event_id):
|
def attend_event(event_id):
|
||||||
user_id = g.user_id
|
user_id = g.user_id # Assuming user_id is stored in Flask's global g object
|
||||||
|
# Check if the event is valid and in the future
|
||||||
event = Event.query.filter(
|
event = Event.query.filter(
|
||||||
Event.id == event_id,
|
Event.id == event_id,
|
||||||
Event.duedate > datetime.now(),
|
Event.duedate > datetime.now(),
|
||||||
|
|
Loading…
Reference in a new issue