Compare commits
3 commits
22c080572e
...
e718ae905d
Author | SHA1 | Date | |
---|---|---|---|
e718ae905d | |||
a63b31da04 | |||
7778626bcb |
7 changed files with 131 additions and 53 deletions
|
@ -52,7 +52,7 @@ update, delete, and be reminded of events with additional advanced features.
|
||||||
- User Event Association (Many to Many)
|
- User Event Association (Many to Many)
|
||||||
- User_id (string, required, foreign key)
|
- User_id (string, required, foreign key)
|
||||||
- Event_id (Integer, required, foreign key)
|
- Event_id (Integer, required, foreign key)
|
||||||
- Notification Made (bool, required)
|
- Notifiyed Made (bool, default: false)
|
||||||
|
|
||||||
|
|
||||||
# API EndPoints
|
# API EndPoints
|
||||||
|
|
19
app.py
19
app.py
|
@ -9,6 +9,8 @@ from middlewares.errorHandlers import handle_auth_error, handle_invalid_token
|
||||||
from flask_jwt_extended.exceptions import NoAuthorizationError
|
from flask_jwt_extended.exceptions import NoAuthorizationError
|
||||||
from jwt.exceptions import InvalidTokenError
|
from jwt.exceptions import InvalidTokenError
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from flask_apscheduler import APScheduler
|
||||||
|
from services.EventNotifyerService import EventNotifyerServer
|
||||||
|
|
||||||
class App:
|
class App:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -18,6 +20,15 @@ class App:
|
||||||
self.set_up_jwt()
|
self.set_up_jwt()
|
||||||
self.register_blueprints()
|
self.register_blueprints()
|
||||||
self.setup_error_handlers()
|
self.setup_error_handlers()
|
||||||
|
self.scheduler = APScheduler()
|
||||||
|
|
||||||
|
def setup_scheduler(self):
|
||||||
|
self.app.config.from_object(self.SchadulerConfig())
|
||||||
|
self.scheduler.init_app(self.app)
|
||||||
|
self.scheduler.start()
|
||||||
|
# Schedule the job
|
||||||
|
self.scheduler.add_job(id='locate_upcoming_events', func=self.locate_upcoming_events, trigger='interval', seconds=1)
|
||||||
|
|
||||||
|
|
||||||
def set_config(self):
|
def set_config(self):
|
||||||
self.app.config.from_object(config.Config)
|
self.app.config.from_object(config.Config)
|
||||||
|
@ -44,6 +55,7 @@ class App:
|
||||||
def run(self):
|
def run(self):
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
self.setup_scheduler() # Setup scheduler
|
||||||
self.app.run(debug=True)
|
self.app.run(debug=True)
|
||||||
|
|
||||||
def print_endpoints(self):
|
def print_endpoints(self):
|
||||||
|
@ -54,6 +66,13 @@ class App:
|
||||||
print(f" Function: {function_name}")
|
print(f" Function: {function_name}")
|
||||||
|
|
||||||
|
|
||||||
|
def SchadulerConfig(object):
|
||||||
|
SCHEDULER_API_ENABLED = True
|
||||||
|
|
||||||
|
def locate_upcoming_events(self):
|
||||||
|
with self.app.app_context():
|
||||||
|
print(EventNotifyerServer.locate_upcoming_events())
|
||||||
|
|
||||||
app_class_instance = App()
|
app_class_instance = App()
|
||||||
app_instance = app_class_instance.app
|
app_instance = app_class_instance.app
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
redis:
|
|
||||||
image: redis:latest
|
|
||||||
ports:
|
|
||||||
- "6379:6379"
|
|
44
migrations/versions/04e41f293ec2_adda_col_notifyed_bool.py
Normal file
44
migrations/versions/04e41f293ec2_adda_col_notifyed_bool.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
"""AddA_col_Notifyed_Bool
|
||||||
|
|
||||||
|
Revision ID: 04e41f293ec2
|
||||||
|
Revises: 902a6851ae27
|
||||||
|
Create Date: 2024-01-08 21:11:13.952001
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '04e41f293ec2'
|
||||||
|
down_revision = '902a6851ae27'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('user_event', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('notified', sa.Boolean(), nullable=True))
|
||||||
|
batch_op.alter_column('user_id',
|
||||||
|
existing_type=sa.VARCHAR(length=36),
|
||||||
|
nullable=False)
|
||||||
|
batch_op.alter_column('event_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('user_event', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('event_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
batch_op.alter_column('user_id',
|
||||||
|
existing_type=sa.VARCHAR(length=36),
|
||||||
|
nullable=True)
|
||||||
|
batch_op.drop_column('notified')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
46
models.py
46
models.py
|
@ -1,28 +1,27 @@
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
from sqlalchemy import Table, Column, Integer, ForeignKey, String
|
from sqlalchemy import Column, Integer, ForeignKey, String, Boolean
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
bcrypt = Bcrypt()
|
bcrypt = Bcrypt()
|
||||||
|
|
||||||
|
class UserEventAssociation(db.Model):
|
||||||
user_event_association = Table('user_event', db.Model.metadata,
|
__tablename__ = 'user_event'
|
||||||
Column('user_id', String(36), ForeignKey('user.id')),
|
user_id = Column(String(36), ForeignKey('user.id'), primary_key=True)
|
||||||
Column('event_id', Integer, ForeignKey('event.id'))
|
event_id = Column(Integer, ForeignKey('event.id'), primary_key=True)
|
||||||
)
|
notified = Column(Boolean, default=False)
|
||||||
|
|
||||||
class Event(db.Model):
|
class Event(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = Column(db.Integer, primary_key=True)
|
||||||
title = db.Column(db.String(100), nullable=False)
|
title = Column(db.String(100), nullable=False)
|
||||||
description = db.Column(db.String(200), nullable=False)
|
description = Column(db.String(200), nullable=False)
|
||||||
location = db.Column(db.String(100), nullable=False)
|
location = Column(db.String(100), nullable=False)
|
||||||
deleted = db.Column(db.Boolean, default=False)
|
deleted = Column(Boolean, default=False)
|
||||||
duedate = db.Column(db.DateTime, nullable=False)
|
duedate = Column(db.DateTime, nullable=False)
|
||||||
created_at = db.Column(db.DateTime, default=db.func.now())
|
created_at = Column(db.DateTime, default=db.func.now())
|
||||||
user_id = db.Column(db.String(36), db.ForeignKey('user.id'), nullable=False)
|
user_id = Column(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', back_populates='events')
|
||||||
|
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
|
@ -36,14 +35,12 @@ 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 = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||||
name = db.Column(db.String(100), nullable=False)
|
name = Column(db.String(100), nullable=False)
|
||||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
email = Column(db.String(120), unique=True, nullable=False)
|
||||||
password_hash = db.Column(db.String(128), nullable=False)
|
password_hash = Column(db.String(128), nullable=False)
|
||||||
location = db.Column(db.String(100), nullable=False)
|
location = Column(db.String(100), nullable=False)
|
||||||
events = db.relationship('Event', secondary=user_event_association, back_populates='users')
|
events = db.relationship('Event', secondary='user_event', back_populates='users')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
|
self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')
|
||||||
|
@ -58,4 +55,3 @@ class User(db.Model):
|
||||||
'email': self.email,
|
'email': self.email,
|
||||||
'location': self.location
|
'location': self.location
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
services/EventNotifyerService.py
Normal file
10
services/EventNotifyerService.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# this Class is for the scheduler
|
||||||
|
#this class will have a function that locates the upcomming events Using the Event service.
|
||||||
|
from services.EventService import EventService
|
||||||
|
|
||||||
|
class EventNotifyerServer:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def locate_upcoming_events():
|
||||||
|
return EventService.get_all_upcomming_events()
|
|
@ -1,9 +1,10 @@
|
||||||
from sqlalchemy.sql import exists
|
from sqlalchemy.sql import exists
|
||||||
from models import db, Event, user_event_association
|
from models import db, Event, UserEventAssociation, User
|
||||||
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
|
from sqlalchemy import func
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
class EventService:
|
class EventService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -31,7 +32,9 @@ class EventService:
|
||||||
if sort_by == 'date':
|
if sort_by == 'date':
|
||||||
query = query.order_by(Event.duedate)
|
query = query.order_by(Event.duedate)
|
||||||
elif sort_by == 'popularity':
|
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())
|
query = query.join(UserEventAssociation, Event.id == UserEventAssociation.event_id)\
|
||||||
|
.group_by(Event.id)\
|
||||||
|
.order_by(func.count(UserEventAssociation.user_id).desc())
|
||||||
elif sort_by == 'creation':
|
elif sort_by == 'creation':
|
||||||
query = query.order_by(Event.created_at)
|
query = query.order_by(Event.created_at)
|
||||||
|
|
||||||
|
@ -82,25 +85,26 @@ class EventService:
|
||||||
return {'error': 'Event not found or already passed'}
|
return {'error': 'Event not found or already passed'}
|
||||||
|
|
||||||
# Check if the user is already associated with the event
|
# Check if the user is already associated with the event
|
||||||
is_already_attending = db.session.query(exists().where(
|
is_already_attending = UserEventAssociation.query.filter_by(
|
||||||
user_event_association.c.user_id == user_id,
|
user_id=user_id,
|
||||||
user_event_association.c.event_id == event_id
|
event_id=event_id
|
||||||
)).scalar()
|
).first()
|
||||||
|
|
||||||
if is_already_attending:
|
if is_already_attending:
|
||||||
return {'error': 'User already attending this event'}
|
return {'error': 'User already attending this event'}
|
||||||
|
|
||||||
# Add the user to the event
|
# Add the user to the event
|
||||||
user = UserService.get_user_by_id(user_id)
|
user_event_association = UserEventAssociation(
|
||||||
if not user:
|
user_id=user_id,
|
||||||
return {'error': 'User not found'}
|
event_id=event_id
|
||||||
|
)
|
||||||
event.users.append(user)
|
db.session.add(user_event_association)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {'message': 'User successfully added to the event'}
|
return {'message': 'User successfully added to the event'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unattend_event(event_id):
|
def unattend_event(event_id):
|
||||||
user_id = g.user_id # Assuming user_id is stored in Flask's global g object
|
user_id = g.user_id # Assuming user_id is stored in Flask's global g object
|
||||||
|
@ -114,21 +118,33 @@ class EventService:
|
||||||
if not event:
|
if not event:
|
||||||
return {'error': 'Event not found or already passed'}
|
return {'error': 'Event not found or already passed'}
|
||||||
|
|
||||||
# Check if the user is already associated with the event
|
user_event_association = UserEventAssociation.query.filter_by(
|
||||||
is_already_attending = db.session.query(exists().where(
|
user_id=user_id,
|
||||||
user_event_association.c.user_id == user_id,
|
event_id=event_id
|
||||||
user_event_association.c.event_id == event_id
|
).first()
|
||||||
)).scalar()
|
|
||||||
|
|
||||||
if not is_already_attending:
|
|
||||||
|
if not user_event_association:
|
||||||
return {'error': 'User not attending this event'}
|
return {'error': 'User not attending this event'}
|
||||||
|
|
||||||
# Remove the user from the event
|
|
||||||
user = UserService.get_user_by_id(user_id)
|
user = UserService.get_user_by_id(user_id)
|
||||||
if not user:
|
if not user:
|
||||||
return {'error': 'User not found'}
|
return {'error': 'User not found'}
|
||||||
|
|
||||||
event.users.remove(user)
|
|
||||||
|
db.session.delete(user_event_association)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {'message': 'User successfully removed from the event'}
|
return {'message': 'User successfully removed from the event'}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_all_upcomming_events():
|
||||||
|
now = datetime.now()
|
||||||
|
upcoming_deadline = now + timedelta(minutes=30)
|
||||||
|
events = Event.query.filter(
|
||||||
|
|
||||||
|
Event.duedate <= upcoming_deadline,
|
||||||
|
Event.deleted == False
|
||||||
|
).all()
|
||||||
|
|
||||||
|
return [event.to_dict() for event in events]
|
Loading…
Reference in a new issue