Merge remote-tracking branch 'origin/develop' into feature/pricelist
This commit is contained in:
commit
11b7f05ad7
|
@ -1,16 +1,32 @@
|
|||
from datetime import datetime, timedelta, timezone
|
||||
from enum import IntEnum
|
||||
from typing import Optional
|
||||
|
||||
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, Job, JobType, Service
|
||||
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()
|
||||
|
||||
|
@ -232,6 +248,8 @@ def update():
|
|||
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()
|
||||
|
||||
|
@ -242,8 +260,7 @@ def assign_job(job: Job, user, value, is_backup=False):
|
|||
if service:
|
||||
service.value = value
|
||||
else:
|
||||
service = Service(user_=user, value=value, is_backup=is_backup, job_=job)
|
||||
db.session.add(service)
|
||||
job.services.append(Service(user_=user, value=value, is_backup=is_backup, job_=job))
|
||||
db.session.commit()
|
||||
|
||||
|
||||
|
@ -264,6 +281,52 @@ def unassign_job(job: Job = None, user=None, service=None, notify=False):
|
|||
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")
|
||||
|
|
|
@ -73,6 +73,7 @@ class Job(db.Model, ModelSerializeMixin):
|
|||
|
||||
event_ = db.relationship("Event", back_populates="jobs")
|
||||
event_id_ = db.Column("event_id", Serial, db.ForeignKey(f"{_table_prefix_}event.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"),)
|
||||
|
||||
|
@ -106,25 +107,33 @@ class Event(db.Model, ModelSerializeMixin):
|
|||
)
|
||||
|
||||
|
||||
class Invite(db.Model, ModelSerializeMixin):
|
||||
__tablename__ = _table_prefix_ + "invite"
|
||||
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
|
||||
sender_id: str = None
|
||||
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
|
||||
invitee_: User = db.relationship("User", foreign_keys="Invite._invitee_id")
|
||||
sender_: User = db.relationship("User", foreign_keys="Invite._sender_id")
|
||||
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)
|
||||
_sender_id = db.Column("sender_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 sender_id(self):
|
||||
return self.sender_.userid
|
||||
def inviter_id(self):
|
||||
return self.inviter_.userid
|
||||
|
||||
@property
|
||||
def transferee_id(self):
|
||||
return self.transferee_.userid if self.transferee_ else None
|
||||
|
|
|
@ -463,7 +463,7 @@ def assign_job(job_id, current_session: Session):
|
|||
current_session: Session sent with Authorization Header
|
||||
|
||||
Returns:
|
||||
HTTP-No-Content or HTTP-error
|
||||
JSON encoded Job or HTTP-error
|
||||
"""
|
||||
data = request.get_json()
|
||||
job = event_controller.get_job(job_id)
|
||||
|
@ -480,7 +480,7 @@ def assign_job(job_id, current_session: Session):
|
|||
event_controller.unassign_job(job, user, notify=user != current_session.user_)
|
||||
except (TypeError, KeyError, ValueError):
|
||||
raise BadRequest
|
||||
return no_content()
|
||||
return jsonify(job)
|
||||
|
||||
|
||||
@EventPlugin.blueprint.route("/events/jobs/<int:job_id>/lock", methods=["POST"])
|
||||
|
@ -510,4 +510,67 @@ def lock_job(job_id, current_session: Session):
|
|||
return no_content()
|
||||
|
||||
|
||||
# TODO: JobTransfer
|
||||
@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/<int:invitation_id>", 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/<int:invitation_id>", 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()
|
||||
|
|
Loading…
Reference in New Issue