Compare commits

...

2 commits

Author SHA1 Message Date
Kfir Dayan 22c080572e started with README.md 2024-01-08 20:15:49 +02:00
Kfir Dayan 5590a793c9 adding more complexicity querying 2024-01-08 15:28:00 +02:00
5 changed files with 161 additions and 39 deletions

View file

@ -1,14 +1,68 @@
### 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
```

View file

@ -0,0 +1,64 @@
"""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 ###

View file

@ -15,10 +15,10 @@ user_event_association = Table('user_event', db.Model.metadata,
class Event(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(200))
location = db.Column(db.String(100))
description = db.Column(db.String(200), nullable=False)
location = db.Column(db.String(100), nullable=False)
deleted = db.Column(db.Boolean, default=False)
duedate = db.Column(db.DateTime)
duedate = db.Column(db.DateTime, nullable=False)
created_at = db.Column(db.DateTime, default=db.func.now())
user_id = db.Column(db.String(36), db.ForeignKey('user.id'), nullable=False)
users = db.relationship('User', secondary=user_event_association, back_populates='events')
@ -37,9 +37,9 @@ class Event(db.Model):
class User(db.Model):
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
name = db.Column(db.String(100))
name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
password_hash = db.Column(db.String(128), nullable=False)
location = db.Column(db.String(100), nullable=False)
events = db.relationship('Event', secondary=user_event_association, back_populates='users')

View file

@ -22,18 +22,17 @@ def create_event():
except Exception as e:
return jsonify({'error': str(e)}), 500
# Get All Events
@eventRoutes.route('/', methods=['GET'])
@eventRoutes.route('', methods=['GET'])
@authenticate_user
def get_events():
try:
user_events = EventService.get_upcoming_events(g.user_id)
location = request.args.get('location')
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
except Exception as e:
return {"error": str(e)}, 500
# Get Event by ID
@eventRoutes.route('/<int:event_id>', methods=['GET'])
@authenticate_user
def get_event(event_id):
@ -42,7 +41,6 @@ def get_event(event_id):
except Exception as e:
return {"error": str(e)}, 500
# Update Event
@eventRoutes.route('/<int:event_id>', methods=['PUT'])
@validate_event_post_request
@authenticate_user
@ -57,7 +55,6 @@ def update_event(event_id):
except Exception as e:
return jsonify({'error': str(e)}), 500
# DELETE Event
@eventRoutes.route('/<int:event_id>', methods=['DELETE'])
@authenticate_user
def delete_event(event_id):
@ -71,24 +68,20 @@ def delete_event(event_id):
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'])
@authenticate_user
def attend_event(event_id):
response = EventService.attend_event(event_id)
# Check if there's an error in the response
if 'error' in response:
return jsonify({'error': response['error']}), 400 # Or another appropriate status code
return jsonify({'error': response['error']}), 400
# If no error, return success response
return jsonify({'message': response['message']}), 200
@eventRoutes.route('/<int:event_id>/unattend', methods=['POST'])
@authenticate_user
def unattend_event(event_id):
response = EventService.unattend_event(event_id)
# Check if there's an error in the response
if 'error' in response:
return jsonify({'error': response['error']}), 400 # Or another appropriate status code
return jsonify({'error': response['error']}), 400
return jsonify({'message': response['message']}), 200

View file

@ -3,6 +3,7 @@ from models import db, Event, user_event_association
from services.UserService import UserService
from datetime import datetime
from flask import g
from sqlalchemy import func
class EventService:
@staticmethod
@ -19,12 +20,23 @@ class EventService:
return new_event
@staticmethod
def get_upcoming_events(user_id):
events=Event.query.filter_by(deleted=False).all()
if(events):
return [event.to_dict() for event in events]
else:
return []
def get_upcoming_events(location=None, sort_by=None):
query = Event.query.filter(
Event.deleted == False,
Event.duedate > datetime.now()
)
if location:
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
def get_event_by_id(event_id, user_id):
@ -36,7 +48,7 @@ class EventService:
@staticmethod
def update_event(event_id, data):
event = Event.query.get(event_id)
event = Event.query.filter_by(user_id=g.user_id, id=event_id).first()
if not event:
return None
event.title = data['title']
@ -48,7 +60,7 @@ class EventService:
@staticmethod
def delete_event(event_id):
event = Event.query.filter_by(id=event_id, deleted=False).first()
event = Event.query.filter_by(id=event_id, user_id=g.user_id, deleted=False).first()
if event:
event.deleted = True
db.session.commit()
@ -59,8 +71,7 @@ class EventService:
@staticmethod
def attend_event(event_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
user_id = g.user_id
event = Event.query.filter(
Event.id == event_id,
Event.duedate > datetime.now(),