diff --git a/migrations/versions/199b40c274e7_alter_table_add_status_change_at_on_.py b/migrations/versions/199b40c274e7_alter_table_add_status_change_at_on_.py new file mode 100644 index 0000000..5bfa0ed --- /dev/null +++ b/migrations/versions/199b40c274e7_alter_table_add_status_change_at_on_.py @@ -0,0 +1,32 @@ +"""alter_table_add_status_change_at_on_event_table + +Revision ID: 199b40c274e7 +Revises: f391490d3fa9 +Create Date: 2023-08-26 18:08:17.283643 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '199b40c274e7' +down_revision = 'f391490d3fa9' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('events', schema=None) as batch_op: + batch_op.add_column(sa.Column('status_change_at', sa.DateTime(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('events', schema=None) as batch_op: + batch_op.drop_column('status_change_at') + + # ### end Alembic commands ### diff --git a/migrations/versions/21069897ab61_alter_table_add_ondelte_cascade_for_.py b/migrations/versions/21069897ab61_alter_table_add_ondelte_cascade_for_.py new file mode 100644 index 0000000..003ffe1 --- /dev/null +++ b/migrations/versions/21069897ab61_alter_table_add_ondelte_cascade_for_.py @@ -0,0 +1,42 @@ +"""alter_table_add_ondelte_cascade_for_event_ticket_table + +Revision ID: 21069897ab61 +Revises: 199b40c274e7 +Create Date: 2023-08-26 18:39:02.825930 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '21069897ab61' +down_revision = '199b40c274e7' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('tickets', schema=None) as batch_op: + batch_op.drop_constraint('tickets_ibfk_1', type_='foreignkey') + batch_op.create_foreign_key(None, 'events', ['event_id'], ['event_id'], ondelete='CASCADE') + + with op.batch_alter_table('transactions', schema=None) as batch_op: + batch_op.drop_constraint('transactions_ibfk_1', type_='foreignkey') + batch_op.create_foreign_key(None, 'tickets', ['ticket_id'], ['ticket_id'], ondelete='CASCADE') + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('transactions', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('transactions_ibfk_1', 'tickets', ['ticket_id'], ['ticket_id']) + + with op.batch_alter_table('tickets', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('tickets_ibfk_1', 'events', ['event_id'], ['event_id']) + + # ### end Alembic commands ### diff --git a/src/config/permission.py b/src/config/permission.py index 2aff313..0f1f997 100644 --- a/src/config/permission.py +++ b/src/config/permission.py @@ -8,7 +8,6 @@ '/users/topup', '/users/withdraw', '/transactions/my', - '/tickets/my' ] admin_permission=[ @@ -20,6 +19,7 @@ ] user_permission=[ + '/tickets/my' ] diff --git a/src/models/Event.py b/src/models/Event.py index 9f12086..1fabba5 100644 --- a/src/models/Event.py +++ b/src/models/Event.py @@ -12,6 +12,7 @@ class Event(db.Model): description= db.Column(db.Text) poster_path = db.Column(db.Text) address= db.Column(db.Text) + status_change_at = db.Column(db.DateTime, nullable=True) user_id = db.Column(db.Integer, db.ForeignKey('users.user_id')) category_id = db.Column(db.Integer, db.ForeignKey('categories.category_id')) category = db.relationship('Category', backref='events') diff --git a/src/models/Ticket.py b/src/models/Ticket.py index 74b2f02..8b45aeb 100644 --- a/src/models/Ticket.py +++ b/src/models/Ticket.py @@ -4,12 +4,12 @@ class Ticket(db.Model): __tablename__ = 'tickets' ticket_id = db.Column(db.Integer, primary_key=True) - event_id = db.Column(db.Integer, db.ForeignKey('events.event_id')) + event_id = db.Column(db.Integer, db.ForeignKey('events.event_id',ondelete='CASCADE')) user_id = db.Column(db.Integer, db.ForeignKey('users.user_id')) ticket_code = db.Column(db.String(6)) is_attended = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=db.func.current_timestamp()) - event = db.relationship("Event", backref="tickets") + event = db.relationship("Event", backref="tickets", cascade="all, delete") user = db.relationship("User", back_populates="tickets") transactions = db.relationship("Transaction", back_populates="ticket") def __init__(self, event_id, user_id, ticket_code): diff --git a/src/models/Transaction.py b/src/models/Transaction.py index 4f63ab9..dbd8cfb 100644 --- a/src/models/Transaction.py +++ b/src/models/Transaction.py @@ -7,10 +7,10 @@ class Transaction(db.Model): type = db.Column(db.String(10), nullable=False) nominal = db.Column(db.Integer, nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('users.user_id')) - ticket_id = db.Column(db.Integer, db.ForeignKey('tickets.ticket_id')) + ticket_id = db.Column(db.Integer, db.ForeignKey('tickets.ticket_id',ondelete='CASCADE')) created_at = db.Column(db.DateTime, default=db.func.current_timestamp()) user = db.relationship("User", back_populates="transactions") - ticket = db.relationship("Ticket", back_populates="transactions") + ticket = db.relationship("Ticket", back_populates="transactions", cascade="all, delete") def __init__(self, type, user_id, nominal): self.type = type self.user_id = user_id diff --git a/src/repositories/CategoryRepository.py b/src/repositories/CategoryRepository.py index e51d744..95faae5 100644 --- a/src/repositories/CategoryRepository.py +++ b/src/repositories/CategoryRepository.py @@ -26,3 +26,5 @@ def deleteCategory(self,id): db.session.delete(category) db.session.commit() return True + def getCategoryByName(self,name): + return Category.query.filter_by(name=name).first() \ No newline at end of file diff --git a/src/repositories/EventRepository.py b/src/repositories/EventRepository.py index bf14ea3..2cfd9c7 100644 --- a/src/repositories/EventRepository.py +++ b/src/repositories/EventRepository.py @@ -69,6 +69,7 @@ def updateStatus(self,event_id,status): event = Event.query.filter_by(event_id=event_id).first() if(not event) :return False event.status = status + event.change_status_at = datetime.now() db.session.commit() return event diff --git a/src/repositories/TicketRepository.py b/src/repositories/TicketRepository.py index e104853..cb3e309 100644 --- a/src/repositories/TicketRepository.py +++ b/src/repositories/TicketRepository.py @@ -1,13 +1,12 @@ from src.models.Ticket import Ticket,db +from sqlalchemy import and_ import string import random class TicketRepository: def getAllTickets(self): - return Ticket.query.all() - + return Ticket.query.filter(Ticket.event_id.isnot(None)).all() def getAllTicketByUserId(self,user_id): - return Ticket.query.filter_by(user_id=user_id).all() - + return Ticket.query.filter(and_(Ticket.user_id == user_id, Ticket.event_id.isnot(None))).all() def getAllTicketByEventId(self,event_id): return Ticket.query.filter_by(event_id=event_id).all() @@ -27,4 +26,4 @@ def attendTicket(self,code): db.session.commit() return ticket def getTicketByCode(self,code): - return Ticket.query.filter_by(ticket_code=code).first() \ No newline at end of file + return Ticket.query.filter(and_(Ticket.ticket_code == code, Ticket.event_id.isnot(None))).first() \ No newline at end of file diff --git a/src/repositories/UserRepository.py b/src/repositories/UserRepository.py index 43e9a24..5daa305 100644 --- a/src/repositories/UserRepository.py +++ b/src/repositories/UserRepository.py @@ -4,9 +4,12 @@ class UserRepository: + def getAllUser(self): return User.query.all() - + def getUserByRole(self,role): + return User.query.filter_by(role=role).all() + def getUserByEmail(self, email): return User.query.filter_by(email=email).first() @@ -60,3 +63,4 @@ def updateBalance(self, id, nominal, operator): db.session.commit() return user + diff --git a/src/services/AuthService.py b/src/services/AuthService.py index 8e89f15..e2d2fba 100644 --- a/src/services/AuthService.py +++ b/src/services/AuthService.py @@ -16,7 +16,7 @@ def failedOrSuccessRequest(status, code, data): "code": code, 'data': data, } - def _sendNotification(self,data): + def _sendNotification(self,data,to): templates = render_template( 'html/registeredEoNotification.html', name=data.name, @@ -25,7 +25,7 @@ def _sendNotification(self,data): sendMail( templates=templates, subject="Ticket Event", - to=data.user.email + to=to ) return True @@ -40,7 +40,8 @@ def registerUser(self, data): return self.failedOrSuccessRequest('failed', 400, 'Validation failed') newUser = user_repository.createNewUser(data) if(newUser.role == 'EVENT_ORGANIZER'): - self._sendNotification(newUser) + userAdmin = user_repository.getUserByRole('ADMIN') + self._sendNotification(newUser,userAdmin[0].email) return self.failedOrSuccessRequest('success', 201, queryResultToDict([newUser])[0]) except ValueError as e: diff --git a/src/services/CategoryService.py b/src/services/CategoryService.py index 846c45d..7ee2616 100644 --- a/src/services/CategoryService.py +++ b/src/services/CategoryService.py @@ -28,6 +28,10 @@ def createCategory(self,data): validate = CreateNewCategoryValidator(**data) if not validate: return self.failedOrSuccessRequest('failed', 400, 'Validation failed') + category = categoryRepository.getCategoryByName(data['name']) + if(category): + return self.failedOrSuccessRequest('failed', 400, 'category already exist') + newCategory = categoryRepository.createNewCategory(data) return self.failedOrSuccessRequest('success', 201, queryResultToDict([newCategory])[0]) except ValueError as e: diff --git a/src/services/EventService.py b/src/services/EventService.py index c5b0154..70c3561 100644 --- a/src/services/EventService.py +++ b/src/services/EventService.py @@ -56,6 +56,12 @@ def createEvent(self,data,file,user_id): return EventService.failedOrSuccessRequest('failed', 400, 'Validation failed') if not file['poster']: return EventService.failedOrSuccessRequest('failed', 400, 'poster is required') + if file['poster'].content_type not in ['image/jpeg','image/png']: + return EventService.failedOrSuccessRequest('failed', 400, 'poster must be image') + if sys.getsizeof(file['poster'].read()) > 2000000: + return EventService.failedOrSuccessRequest('failed', 400, 'poster must be less than 2mb') + # + poster = upload_file(file['poster']) newEvent = eventRepository.createNewEvent(**data,poster_path=poster,user_id=user_id) return EventService.failedOrSuccessRequest('success', 201, queryResultToDict([newEvent])[0]) diff --git a/src/services/TicketService.py b/src/services/TicketService.py index 6814c59..6f78510 100644 --- a/src/services/TicketService.py +++ b/src/services/TicketService.py @@ -122,7 +122,7 @@ def attendTicket(self,data,user_id): ticket = ticketRepository.getTicketByCode(data['ticket_code']) if(not ticket): return self.failedOrSuccessRequest('failed', 400, 'ticket not found') - if(ticket.user_id != user_id): + if(ticket.event.user_id != user_id): return self.failedOrSuccessRequest('failed', 400, 'ticket not belong to you') if(ticket.is_attended): return self.failedOrSuccessRequest('failed', 400, 'ticket already attended') @@ -132,4 +132,4 @@ def attendTicket(self,data,user_id): except ValueError as e: return self.failedOrSuccessRequest('failed', 400, errorHandler(e.errors())) except Exception as e: - return self.failedOrSuccessRequest('failed', 500, str(e)) \ No newline at end of file + return self.failedOrSuccessRequest('failed', 500, str(e))