From b94319c38f1163743ec21f7cc656113ef4910b60 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sun, 28 Nov 2021 22:29:12 +0100 Subject: [PATCH] chore(plugins) Split of events plugin --- flaschengeist/plugins/events/__init__.py | 22 - .../plugins/events/event_controller.py | 394 ------------- flaschengeist/plugins/events/models.py | 140 ----- flaschengeist/plugins/events/permissions.py | 28 - flaschengeist/plugins/events/routes.py | 528 ------------------ setup.py | 1 - 6 files changed, 1113 deletions(-) delete mode 100644 flaschengeist/plugins/events/__init__.py delete mode 100644 flaschengeist/plugins/events/event_controller.py delete mode 100644 flaschengeist/plugins/events/models.py delete mode 100644 flaschengeist/plugins/events/permissions.py delete mode 100644 flaschengeist/plugins/events/routes.py diff --git a/flaschengeist/plugins/events/__init__.py b/flaschengeist/plugins/events/__init__.py deleted file mode 100644 index c86a214..0000000 --- a/flaschengeist/plugins/events/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Events plugin - -Provides duty schedule / duty roster functions -""" -from flask import Blueprint, current_app -from werkzeug.local import LocalProxy - -from flaschengeist.plugins import Plugin -from . import permissions, models - - -class EventPlugin(Plugin): - name = "events" - id = "dev.flaschengeist.events" - plugin = LocalProxy(lambda: current_app.config["FG_PLUGINS"][EventPlugin.name]) - permissions = permissions.permissions - blueprint = Blueprint(name, __name__) - models = models - - def __init__(self, cfg): - super(EventPlugin, self).__init__(cfg) - from . import routes diff --git a/flaschengeist/plugins/events/event_controller.py b/flaschengeist/plugins/events/event_controller.py deleted file mode 100644 index 432a5a8..0000000 --- a/flaschengeist/plugins/events/event_controller.py +++ /dev/null @@ -1,394 +0,0 @@ -from datetime import datetime, timedelta, timezone -from enum import IntEnum -from typing import Optional, Tuple - -from werkzeug.exceptions import BadRequest, Conflict, NotFound -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm.util import was_deleted - -from flaschengeist import logger -from flaschengeist.database import db -from flaschengeist.plugins.events import EventPlugin -from flaschengeist.plugins.events.models import EventType, Event, Invitation, Job, JobType, Service -from flaschengeist.utils.scheduler import scheduled - - -# STUB -def _(x): - return x - - -class NotifyType(IntEnum): - # Invitations 0x00..0x0F - INVITE = 0x01 - TRANSFER = 0x02 - # Invitation responsed 0x10..0x1F - INVITATION_ACCEPTED = 0x10 - INVITATION_REJECTED = 0x11 - - -def update(): - db.session.commit() - - -def get_event_types(): - return EventType.query.all() - - -def get_event_type(identifier): - """Get EventType by ID (int) or name (string)""" - - if isinstance(identifier, int): - et = EventType.query.get(identifier) - elif isinstance(identifier, str): - et = EventType.query.filter(EventType.name == identifier).one_or_none() - else: - logger.debug("Invalid identifier type for EventType") - raise BadRequest - if not et: - raise NotFound - return et - - -def create_event_type(name): - try: - event = EventType(name=name) - db.session.add(event) - db.session.commit() - return event - except IntegrityError: - raise Conflict("Name already exists") - - -def rename_event_type(identifier, new_name): - event_type = get_event_type(identifier) - event_type.name = new_name - try: - db.session.commit() - except IntegrityError: - raise Conflict("Name already exists") - - -def delete_event_type(name): - event_type = get_event_type(name) - db.session.delete(event_type) - try: - db.session.commit() - except IntegrityError: - raise BadRequest("Type still in use") - - -def get_job_types(): - return JobType.query.all() - - -def get_job_type(type_id): - job_type = JobType.query.get(type_id) - print(job_type) - if not job_type: - raise NotFound - return job_type - - -def create_job_type(name): - try: - job_type = JobType(name=name) - db.session.add(job_type) - db.session.commit() - return job_type - except IntegrityError: - raise BadRequest("Name already exists") - - -def rename_job_type(name, new_name): - job_type = get_job_type(name) - job_type.name = new_name - try: - db.session.commit() - except IntegrityError: - raise BadRequest("Name already exists") - - -def delete_job_type(name): - job_type = get_job_type(name) - db.session.delete(job_type) - try: - db.session.commit() - except IntegrityError: - raise BadRequest("Type still in use") - - -def clear_backup(event: Event): - for job in event.jobs: - services = [] - for service in job.services: - if not service.is_backup: - services.append(service) - job.services = services - - -def get_event(event_id, with_backup=False) -> Event: - event = Event.query.get(event_id) - if event is None: - raise NotFound - if not with_backup: - clear_backup(event) - return event - - -def get_templates(): - return Event.query.filter(Event.is_template == True).all() - - -def get_events( - start: Optional[datetime] = None, - end: Optional[datetime] = None, - limit: Optional[int] = None, - offset: Optional[int] = None, - descending: Optional[bool] = False, - with_backup=False, -) -> Tuple[int, list[Event]]: - """Query events which start from begin until end - Args: - start (datetime): Earliest start - end (datetime): Latest start - with_backup (bool): Export also backup services - - Returns: collection of Event objects - """ - query = Event.query.filter(Event.is_template.__eq__(False)) - if start is not None: - query = query.filter(start <= Event.start) - if end is not None: - query = query.filter(Event.start < end) - elif start is None: - # Neither start nor end was given - query = query.filter(datetime.now() <= Event.start) - if descending: - query = query.order_by(Event.start.desc()) - else: - query = query.order_by(Event.start) - count = query.count() - if limit is not None: - query = query.limit(limit) - if offset is not None and offset > 0: - query = query.offset(offset) - events = query.all() - if not with_backup: - for event in events: - clear_backup(event) - return count, events - - -def delete_event(event_id): - """Delete event with given ID - Args: - event_id: id of Event to delete - - Raises: - NotFound if not found - """ - event = get_event(event_id, True) - for job in event.jobs: - delete_job(job) - db.session.delete(event) - db.session.commit() - - -def create_event(event_type, start, end=None, jobs=[], is_template=None, name=None, description=None): - try: - logger.debug(event_type) - event = Event( - start=start, - end=end, - name=name, - description=description, - type=event_type, - is_template=is_template, - jobs=jobs, - ) - db.session.add(event) - db.session.commit() - return event - except IntegrityError: - logger.debug("Database error when creating new event", exc_info=True) - raise BadRequest - - -def get_job(job_id, event_id=None) -> Job: - query = Job.query.filter(Job.id == job_id) - if event_id is not None: - query = query.filter(Job.event_id_ == event_id) - job = query.one_or_none() - if job is None: - raise NotFound - return job - - -def get_jobs(user, start=None, end=None, limit=None, offset=None, descending=None) -> Tuple[int, list[Job]]: - query = Job.query.join(Service).filter(Service.user_ == user) - if start is not None: - query = query.filter(start <= Job.end) - if end is not None: - query = query.filter(end >= Job.start) - if descending is not None: - query = query.order_by(Job.start.desc(), Job.type_id_) - else: - query = query.order_by(Job.start, Job.type_id_) - count = query.count() - if limit is not None: - query = query.limit(limit) - if offset is not None: - query = query.offset(offset) - return count, query.all() - - -def add_job(event, job_type, required_services, start, end=None, comment=None): - job = Job( - required_services=required_services, - type=job_type, - start=start, - end=end, - comment=comment, - ) - event.jobs.append(job) - update() - return job - - -def update(): - try: - db.session.commit() - except IntegrityError: - logger.debug( - "Error, looks like a Job with that type already exists on an event", - exc_info=True, - ) - raise BadRequest() - - -def delete_job(job: Job): - for service in job.services: - unassign_job(service=service, notify=True) - for invitation in job.invitations_: - respond_invitation(invitation, False) - db.session.delete(job) - db.session.commit() - - -def assign_job(job: Job, user, value, is_backup=False): - assert value > 0 - service = Service.query.get((job.id, user.id_)) - if service: - service.value = value - else: - job.services.append(Service(user_=user, value=value, is_backup=is_backup, job_=job)) - db.session.commit() - - -def unassign_job(job: Job = None, user=None, service=None, notify=False): - if service is None: - assert job is not None and user is not None - service = Service.query.get((job.id, user.id_)) - else: - user = service.user_ - if not service: - raise BadRequest - - event_id = service.job_.event_id_ - - db.session.delete(service) - db.session.commit() - if notify: - EventPlugin.plugin.notify(user, "Your assignmet was cancelled", {"event_id": event_id}) - - -def invite(job: Job, invitee, inviter, transferee=None): - inv = Invitation(job_=job, inviter_=inviter, invitee_=invitee, transferee_=transferee) - db.session.add(inv) - update() - if transferee is None: - EventPlugin.plugin.notify(invitee, _("Job invitation"), {"type": NotifyType.INVITE, "invitation": inv.id}) - else: - EventPlugin.plugin.notify(invitee, _("Job transfer"), {"type": NotifyType.TRANSFER, "invitation": inv.id}) - return inv - - -def get_invitation(id: int): - inv: Invitation = Invitation.query.get(id) - if inv is None: - raise NotFound - return inv - - -def cancel_invitation(inv: Invitation): - db.session.delete(inv) - db.session.commit() - - -def respond_invitation(invite: Invitation, accepted=True): - inviter = invite.inviter_ - job = invite.job_ - - db.session.delete(invite) - db.session.commit() - if not was_deleted(invite): - raise Conflict - - if not accepted: - EventPlugin.plugin.notify( - inviter, - _("Invitation rejected"), - { - "type": NotifyType.INVITATION_REJECTED, - "event": job.event_id_, - "job": invite.job_id, - "invitee": invite.invitee_id, - }, - ) - else: - if invite.transferee_id is None: - assign_job(job, invite.invitee_, 1) - else: - service = filter(lambda s: s.userid == invite.transferee_id, job.services) - if not service: - raise Conflict - unassign_job(job, invite.transferee_, service[0], True) - assign_job(job, invite.invitee_, service[0].value) - EventPlugin.plugin.notify( - inviter, - _("Invitation accepted"), - { - "type": NotifyType.INVITATION_ACCEPTED, - "event": job.event_id_, - "job": invite.job_id, - "invitee": invite.invitee_id, - }, - ) - - -@scheduled -def assign_backups(): - logger.debug("Notifications") - now = datetime.now(tz=timezone.utc) - # now + backup_time + next cron tick - start = now + timedelta(hours=16) + timedelta(minutes=30) - services = Service.query.filter(Service.is_backup == True).join(Job).filter(Job.start <= start).all() - for service in services: - if service.job_.start <= now or service.job_.is_full(): - EventPlugin.plugin.notify( - service.user_, - "Your backup assignment was cancelled.", - {"event_id": service.job_.event_id_}, - ) - logger.debug(f"Service is outdated or full, removing. {service.serialize()}") - db.session.delete(service) - else: - service.is_backup = False - logger.debug(f"Service not full, assigning backup. {service.serialize()}") - EventPlugin.plugin.notify( - service.user_, - "Your backup assignment was accepted.", - {"event_id": service.job_.event_id_}, - ) - db.session.commit() diff --git a/flaschengeist/plugins/events/models.py b/flaschengeist/plugins/events/models.py deleted file mode 100644 index 97c06ca..0000000 --- a/flaschengeist/plugins/events/models.py +++ /dev/null @@ -1,140 +0,0 @@ -from __future__ import annotations # TODO: Remove if python requirement is >= 3.10 - -from datetime import datetime -from typing import Optional, Union - -from sqlalchemy import UniqueConstraint - -from flaschengeist.models import ModelSerializeMixin, UtcDateTime, Serial -from flaschengeist.models.user import User -from flaschengeist.database import db - -######### -# Types # -######### - -_table_prefix_ = "events_" - - -class EventType(db.Model, ModelSerializeMixin): - __tablename__ = _table_prefix_ + "event_type" - id: int = db.Column(Serial, primary_key=True) - name: str = db.Column(db.String(30), nullable=False, unique=True) - - -class JobType(db.Model, ModelSerializeMixin): - __tablename__ = _table_prefix_ + "job_type" - id: int = db.Column(Serial, primary_key=True) - name: str = db.Column(db.String(30), nullable=False, unique=True) - - -######## -# Jobs # -######## - - -class Service(db.Model, ModelSerializeMixin): - __tablename__ = _table_prefix_ + "service" - userid: str = "" - is_backup: bool = db.Column(db.Boolean, default=False) - value: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False) - - _job_id = db.Column( - "job_id", - Serial, - db.ForeignKey(f"{_table_prefix_}job.id"), - nullable=False, - primary_key=True, - ) - _user_id = db.Column("user_id", Serial, db.ForeignKey("user.id"), nullable=False, primary_key=True) - - user_: User = db.relationship("User") - job_: Job = db.relationship("Job") - - @property - def userid(self): - return self.user_.userid - - -class Job(db.Model, ModelSerializeMixin): - __tablename__ = _table_prefix_ + "job" - - id: int = db.Column(Serial, primary_key=True) - start: datetime = db.Column(UtcDateTime, nullable=False) - end: Optional[datetime] = db.Column(UtcDateTime) - type: Union[JobType, int] = db.relationship("JobType") - comment: Optional[str] = db.Column(db.String(256)) - locked: bool = db.Column(db.Boolean(), default=False, nullable=False) - services: list[Service] = db.relationship( - "Service", back_populates="job_", cascade="save-update, merge, delete, delete-orphan" - ) - required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False) - - event_ = db.relationship("Event", back_populates="jobs") - event_id_ = db.Column("event_id", Serial, db.ForeignKey(f"{_table_prefix_}event.id"), nullable=False) - type_id_ = db.Column("type_id", Serial, db.ForeignKey(f"{_table_prefix_}job_type.id"), nullable=False) - - invitations_ = db.relationship("Invitation", cascade="all,delete,delete-orphan", back_populates="job_") - - __table_args__ = (UniqueConstraint("type_id", "start", "event_id", name="_type_start_uc"),) - - -########## -# Events # -########## -class Event(db.Model, ModelSerializeMixin): - """Model for an Event""" - - __tablename__ = _table_prefix_ + "event" - id: int = db.Column(Serial, primary_key=True) - start: datetime = db.Column(UtcDateTime, nullable=False) - end: Optional[datetime] = db.Column(UtcDateTime) - name: Optional[str] = db.Column(db.String(255)) - description: Optional[str] = db.Column(db.String(512)) - type: Union[EventType, int] = db.relationship("EventType") - is_template: bool = db.Column(db.Boolean, default=False) - jobs: list[Job] = db.relationship( - "Job", - back_populates="event_", - cascade="all,delete,delete-orphan", - order_by="[Job.start, Job.end]", - ) - # Protected for internal use - _type_id = db.Column( - "type_id", - Serial, - db.ForeignKey(f"{_table_prefix_}event_type.id", ondelete="CASCADE"), - nullable=False, - ) - - -class Invitation(db.Model, ModelSerializeMixin): - __tablename__ = _table_prefix_ + "invitation" - - id: int = db.Column(Serial, primary_key=True) - job_id: int = db.Column(Serial, db.ForeignKey(_table_prefix_ + "job.id"), nullable=False) - # Dummy properties for API export - invitee_id: str = None # User who was invited to take over - inviter_id: str = None # User who invited the invitee - transferee_id: Optional[str] = None # In case of a transfer: The user who is transfered out of the job - # Not exported properties for backend use - job_: Job = db.relationship(Job, foreign_keys="Invitation.job_id") - invitee_: User = db.relationship("User", foreign_keys="Invitation._invitee_id") - inviter_: User = db.relationship("User", foreign_keys="Invitation._inviter_id") - transferee_: User = db.relationship("User", foreign_keys="Invitation._transferee_id") - # Protected properties needed for internal use - _invitee_id = db.Column("invitee_id", Serial, db.ForeignKey("user.id"), nullable=False) - _inviter_id = db.Column("inviter_id", Serial, db.ForeignKey("user.id"), nullable=False) - _transferee_id = db.Column("transferee_id", Serial, db.ForeignKey("user.id")) - - @property - def invitee_id(self): - return self.invitee_.userid - - @property - def inviter_id(self): - return self.inviter_.userid - - @property - def transferee_id(self): - return self.transferee_.userid if self.transferee_ else None diff --git a/flaschengeist/plugins/events/permissions.py b/flaschengeist/plugins/events/permissions.py deleted file mode 100644 index 3eb81b6..0000000 --- a/flaschengeist/plugins/events/permissions.py +++ /dev/null @@ -1,28 +0,0 @@ -CREATE = "events_create" -"""Can create events""" - -EDIT = "events_edit" -"""Can edit events""" - -DELETE = "events_delete" -"""Can delete events""" - -EVENT_TYPE = "events_event_type" -"""Can create and edit EventTypes""" - -JOB_TYPE = "events_job_type" -"""Can create and edit JobTypes""" - -ASSIGN = "events_assign" -"""Can self assign to jobs""" - -ASSIGN_OTHER = "events_assign_other" -"""Can assign other users to jobs""" - -SEE_BACKUP = "events_see_backup" -"""Can see users assigned as backup""" - -LOCK_JOBS = "events_lock_jobs" -"""Can lock jobs, no further services can be assigned or unassigned""" - -permissions = [value for key, value in globals().items() if not key.startswith("_")] diff --git a/flaschengeist/plugins/events/routes.py b/flaschengeist/plugins/events/routes.py deleted file mode 100644 index fe7b2f9..0000000 --- a/flaschengeist/plugins/events/routes.py +++ /dev/null @@ -1,528 +0,0 @@ -from http.client import NO_CONTENT -from flask import request, jsonify -from werkzeug.exceptions import BadRequest, NotFound, Forbidden - -from flaschengeist.models.session import Session -from flaschengeist.utils.decorators import login_required -from flaschengeist.utils.datetime import from_iso_format -from flaschengeist.controller import userController - -from . import event_controller, permissions, EventPlugin -from ...utils.HTTP import get_filter_args, no_content - - -def dict_get(self, key, default=None, type=None): - """Same as .get from MultiDict""" - try: - rv = self[key] - except KeyError: - return default - if type is not None: - try: - rv = type(rv) - except ValueError: - rv = default - return rv - - -@EventPlugin.blueprint.route("/events/templates", methods=["GET"]) -@login_required() -def get_templates(current_session): - return jsonify(event_controller.get_templates()) - - -@EventPlugin.blueprint.route("/events/event-types", methods=["GET"]) -@EventPlugin.blueprint.route("/events/event-types/", methods=["GET"]) -@login_required() -def get_event_types(current_session, identifier=None): - """Get EventType(s) - - Route: ``/events/event-types`` | Method: ``GET`` - Route: ``/events/event-types/`` | Method: ``GET`` - - Args: - current_session: Session sent with Authorization Header - identifier: If querying a specific EventType - - Returns: - JSON encoded (list of) EventType(s) or HTTP-error - """ - if identifier: - result = event_controller.get_event_type(identifier) - else: - result = event_controller.get_event_types() - return jsonify(result) - - -@EventPlugin.blueprint.route("/events/event-types", methods=["POST"]) -@login_required(permission=permissions.EVENT_TYPE) -def new_event_type(current_session): - """Create a new EventType - - Route: ``/events/event-types`` | Method: ``POST`` - - POST-data: ``{name: string}`` - - Args: - current_session: Session sent with Authorization Header - - Returns: - HTTP-Created or HTTP-error - """ - data = request.get_json() - if "name" not in data: - raise BadRequest - event_type = event_controller.create_event_type(data["name"]) - return jsonify(event_type) - - -@EventPlugin.blueprint.route("/events/event-types/", methods=["PUT", "DELETE"]) -@login_required(permission=permissions.EVENT_TYPE) -def modify_event_type(identifier, current_session): - """Rename or delete an event type - - Route: ``/events/event-types/`` | Method: ``PUT`` or ``DELETE`` - - POST-data: (if renaming) ``{name: string}`` - - Args: - identifier: Identifier of the EventType - current_session: Session sent with Authorization Header - - Returns: - HTTP-NoContent or HTTP-error - """ - if request.method == "DELETE": - event_controller.delete_event_type(identifier) - else: - data = request.get_json() - if "name" not in data: - raise BadRequest("Parameter missing in data") - event_controller.rename_event_type(identifier, data["name"]) - return "", NO_CONTENT - - -@EventPlugin.blueprint.route("/events/job-types", methods=["GET"]) -@login_required() -def get_job_types(current_session): - """Get all JobTypes - - Route: ``/events/job-types`` | Method: ``GET`` - - Args: - current_session: Session sent with Authorization Header - - Returns: - JSON encoded list of JobType HTTP-error - """ - types = event_controller.get_job_types() - return jsonify(types) - - -@EventPlugin.blueprint.route("/events/job-types", methods=["POST"]) -@login_required(permission=permissions.JOB_TYPE) -def new_job_type(current_session): - """Create a new JobType - - Route: ``/events/job-types`` | Method: ``POST`` - - POST-data: ``{name: string}`` - - Args: - current_session: Session sent with Authorization Header - - Returns: - JSON encoded JobType or HTTP-error - """ - data = request.get_json() - if "name" not in data: - raise BadRequest - jt = event_controller.create_job_type(data["name"]) - return jsonify(jt) - - -@EventPlugin.blueprint.route("/events/job-types/", methods=["PUT", "DELETE"]) -@login_required(permission=permissions.JOB_TYPE) -def modify_job_type(type_id, current_session): - """Rename or delete a JobType - - Route: ``/events/job-types/`` | Method: ``PUT`` or ``DELETE`` - - POST-data: (if renaming) ``{name: string}`` - - Args: - type_id: Identifier of the JobType - current_session: Session sent with Authorization Header - - Returns: - HTTP-NoContent or HTTP-error - """ - if request.method == "DELETE": - event_controller.delete_job_type(type_id) - else: - data = request.get_json() - if "name" not in data: - raise BadRequest("Parameter missing in data") - event_controller.rename_job_type(type_id, data["name"]) - return "", NO_CONTENT - - -@EventPlugin.blueprint.route("/events/", methods=["GET"]) -@login_required() -def get_event(event_id, current_session): - """Get event by id - - Route: ``/events/`` | Method: ``GET`` - - Args: - event_id: ID identifying the event - current_session: Session sent with Authorization Header - - Returns: - JSON encoded event object - """ - event = event_controller.get_event( - event_id, - with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP), - ) - return jsonify(event) - - -@EventPlugin.blueprint.route("/events", methods=["GET"]) -@login_required() -def get_events(current_session): - count, result = event_controller.get_events( - *get_filter_args(), - with_backup=current_session.user_.has_permission(permissions.SEE_BACKUP), - ) - return jsonify({"count": count, "result": result}) - - -def _add_job(event, data): - try: - start = from_iso_format(data["start"]) - end = dict_get(data, "end", None, type=from_iso_format) - required_services = data["required_services"] - job_type = data["type"] - if isinstance(job_type, dict): - job_type = data["type"]["id"] - except (KeyError, ValueError): - raise BadRequest("Missing or invalid POST parameter") - - job_type = event_controller.get_job_type(job_type) - event_controller.add_job( - event, - job_type, - required_services, - start, - end, - comment=dict_get(data, "comment", None, str), - ) - - -@EventPlugin.blueprint.route("/events", methods=["POST"]) -@login_required(permission=permissions.CREATE) -def create_event(current_session): - """Create an new event - - Route: ``/events`` | Method: ``POST`` - - POST-data: See interfaces for Event, can already contain jobs - - Args: - current_session: Session sent with Authorization Header - - Returns: - JSON encoded Event object or HTTP-error - """ - data = request.get_json() - try: - start = from_iso_format(data["start"]) - end = dict_get(data, "end", None, type=from_iso_format) - data_type = data["type"] - if isinstance(data_type, dict): - data_type = data["type"]["id"] - event_type = event_controller.get_event_type(data_type) - except KeyError: - raise BadRequest("Missing POST parameter") - except (NotFound, ValueError): - raise BadRequest("Invalid parameter") - - event = event_controller.create_event( - start=start, - end=end, - name=dict_get(data, "name", None), - is_template=dict_get(data, "is_template", None), - event_type=event_type, - description=dict_get(data, "description", None), - ) - if "jobs" in data: - for job in data["jobs"]: - _add_job(event, job) - - return jsonify(event) - - -@EventPlugin.blueprint.route("/events/", methods=["PUT"]) -@login_required(permission=permissions.EDIT) -def modify_event(event_id, current_session): - """Modify an event - - Route: ``/events/`` | Method: ``PUT`` - - POST-data: See interfaces for Event, can already contain slots - - Args: - event_id: Identifier of the event - current_session: Session sent with Authorization Header - - Returns: - JSON encoded Event object or HTTP-error - """ - event = event_controller.get_event(event_id) - data = request.get_json() - event.start = dict_get(data, "start", event.start, type=from_iso_format) - event.end = dict_get(data, "end", event.end, type=from_iso_format) - event.name = dict_get(data, "name", event.name, type=str) - event.description = dict_get(data, "description", event.description, type=str) - if "type" in data: - event_type = event_controller.get_event_type(data["type"]) - event.type = event_type - - event_controller.update() - return jsonify(event) - - -@EventPlugin.blueprint.route("/events/", methods=["DELETE"]) -@login_required(permission=permissions.DELETE) -def delete_event(event_id, current_session): - """Delete an event - - Route: ``/events/`` | Method: ``DELETE`` - - Args: - event_id: Identifier of the event - current_session: Session sent with Authorization Header - - Returns: - HTTP-NoContent or HTTP-error - """ - event_controller.delete_event(event_id) - return "", NO_CONTENT - - -@EventPlugin.blueprint.route("/events//jobs", methods=["POST"]) -@login_required(permission=permissions.EDIT) -def add_job(event_id, current_session): - """Add an new Job to an Event / EventSlot - - Route: ``/events//jobs`` | Method: ``POST`` - - POST-data: See Job - - Args: - event_id: Identifier of the event - current_session: Session sent with Authorization Header - - Returns: - JSON encoded Event object or HTTP-error - """ - event = event_controller.get_event(event_id) - _add_job(event, request.get_json()) - return jsonify(event) - - -@EventPlugin.blueprint.route("/events//jobs/", methods=["DELETE"]) -@login_required(permission=permissions.DELETE) -def delete_job(event_id, job_id, current_session): - """Delete a Job - - Route: ``/events//jobs/`` | Method: ``DELETE`` - - Args: - event_id: Identifier of the event - job_id: Identifier of the Job - current_session: Session sent with Authorization Header - - Returns: - HTTP-no-content or HTTP error - """ - job = event_controller.get_job(job_id, event_id) - event_controller.delete_job(job) - return no_content() - - -@EventPlugin.blueprint.route("/events//jobs/", methods=["PUT"]) -@login_required() -def update_job(event_id, job_id, current_session: Session): - """Edit Job - - Route: ``/events//jobs/`` | Method: ``PUT`` - - POST-data: See TS interface for Job - - Args: - event_id: Identifier of the event - job_id: Identifier of the Job - current_session: Session sent with Authorization Header - - Returns: - JSON encoded Job object or HTTP-error - """ - if not current_session.user_.has_permission(permissions.EDIT): - raise Forbidden - - data = request.get_json() - if not data: - raise BadRequest - - job = event_controller.get_job(job_id, event_id) - try: - if "type" in data: - job.type = event_controller.get_job_type(data["type"]) - job.start = from_iso_format(data.get("start", job.start)) - job.end = from_iso_format(data.get("end", job.end)) - job.comment = str(data.get("comment", job.comment)) - job.locked = bool(data.get("locked", job.locked)) - job.required_services = float(data.get("required_services", job.required_services)) - event_controller.update() - except NotFound: - raise BadRequest("Invalid JobType") - except ValueError: - raise BadRequest("Invalid POST data") - - return jsonify(job) - - -@EventPlugin.blueprint.route("/events/jobs", methods=["GET"]) -@login_required() -def get_jobs(current_session: Session): - count, result = event_controller.get_jobs(current_session.user_, *get_filter_args()) - return jsonify({"count": count, "result": result}) - - -@EventPlugin.blueprint.route("/events/jobs//assign", methods=["POST"]) -@login_required() -def assign_job(job_id, current_session: Session): - """Assign / unassign user to the Job - - Route: ``/events/jobs//assign`` | Method: ``POST`` - - POST-data: a Service object, see TS interface for Service - - Args: - job_id: Identifier of the Job - current_session: Session sent with Authorization Header - - Returns: - JSON encoded Job or HTTP-error - """ - data = request.get_json() - job = event_controller.get_job(job_id) - try: - user = userController.get_user(data["userid"]) - value = data["value"] - if (user == current_session.user_ and not user.has_permission(permissions.ASSIGN)) or ( - user != current_session.user_ and not current_session.user_.has_permission(permissions.ASSIGN_OTHER) - ): - raise Forbidden - if value > 0: - event_controller.assign_job(job, user, value, data.get("is_backup", False)) - else: - event_controller.unassign_job(job, user, notify=user != current_session.user_) - except (TypeError, KeyError, ValueError): - raise BadRequest - return jsonify(job) - - -@EventPlugin.blueprint.route("/events/jobs//lock", methods=["POST"]) -@login_required(permissions.LOCK_JOBS) -def lock_job(job_id, current_session: Session): - """Lock / unlock the Job - - Route: ``/events/jobs//lock`` | Method: ``POST`` - - POST-data: ``{locked: boolean}`` - - Args: - job_id: Identifier of the Job - current_session: Session sent with Authorization Header - - Returns: - HTTP-No-Content or HTTP-error - """ - data = request.get_json() - job = event_controller.get_job(job_id) - try: - locked = bool(userController.get_user(data["locked"])) - job.locked = locked - event_controller.update() - except (TypeError, KeyError, ValueError): - raise BadRequest - return no_content() - - -@EventPlugin.blueprint.route("/events/invitations", methods=["POST"]) -@login_required() -def invite(current_session: Session): - """Invite an user to a job or transfer job - - Route: ``/events/invites`` | Method: ``POST`` - - POST-data: ``{job: number, invitees: string[], is_transfer?: boolean}`` - - Args: - current_session: Session sent with Authorization Header - - Returns: - List of Invitation objects or HTTP-error - """ - data = request.get_json() - transferee = data.get("transferee", None) - if ( - transferee is not None - and transferee != current_session.userid - and not current_session.user_.has_permission(permissions.ASSIGN_OTHER) - ): - raise Forbidden - - try: - job = event_controller.get_job(data["job"]) - if not isinstance(data["invitees"], list): - raise BadRequest - return jsonify( - [ - event_controller.invite(job, invitee, current_session.user_, transferee) - for invitee in [userController.get_user(uid) for uid in data["invitees"]] - ] - ) - except (TypeError, KeyError, ValueError): - raise BadRequest - - -@EventPlugin.blueprint.route("/events/invitations/", methods=["GET"]) -@login_required() -def get_invitation(invitation_id: int, current_session: Session): - inv = event_controller.get_invitation(invitation_id) - if current_session.userid not in [inv.invitee_id, inv.inviter_id, inv.transferee_id]: - raise Forbidden - return jsonify(inv) - - -@EventPlugin.blueprint.route("/events/invitations/", methods=["DELETE", "PUT"]) -@login_required() -def respond_invitation(invitation_id: int, current_session: Session): - inv = event_controller.get_invitation(invitation_id) - if request.method == "DELETE": - if current_session.userid == inv.invitee_id: - event_controller.respond_invitation(inv, False) - elif current_session.userid == inv.inviter_id: - event_controller.cancel_invitation(inv) - else: - raise Forbidden - else: - # maybe validate data is something like ({accepted: true}) - if current_session.userid != inv.invitee_id: - raise Forbidden - event_controller.respond_invitation(inv) - return no_content() diff --git a/setup.py b/setup.py index 6bf3a67..4728772 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,6 @@ setup( "users = flaschengeist.plugins.users:UsersPlugin", "roles = flaschengeist.plugins.roles:RolesPlugin", "balance = flaschengeist.plugins.balance:BalancePlugin", - "events = flaschengeist.plugins.events:EventPlugin", "mail = flaschengeist.plugins.message_mail:MailMessagePlugin", "pricelist = flaschengeist.plugins.pricelist:PriceListPlugin", ],