Compare commits

..

6 Commits

7 changed files with 74 additions and 43 deletions

View File

@ -2,6 +2,7 @@ from datetime import datetime, timedelta, timezone
from enum import IntEnum from enum import IntEnum
from typing import Optional, Tuple from typing import Optional, Tuple
from flaschengeist.models import UtcDateTime from flaschengeist.models import UtcDateTime
from flaschengeist.models.user import User
from werkzeug.exceptions import BadRequest, Conflict, NotFound from werkzeug.exceptions import BadRequest, Conflict, NotFound
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
@ -99,7 +100,6 @@ def get_job_types():
def get_job_type(type_id): def get_job_type(type_id):
job_type = JobType.query.get(type_id) job_type = JobType.query.get(type_id)
print(job_type)
if not job_type: if not job_type:
raise NotFound raise NotFound
return job_type return job_type
@ -250,9 +250,9 @@ def get_jobs(user, start=None, end=None, limit=None, offset=None, descending=Non
if end is not None: if end is not None:
query = query.filter(end >= Job.start) query = query.filter(end >= Job.start)
if descending is not None: if descending is not None:
query = query.order_by(Job.start.desc(), Job.type_id_) query = query.order_by(Job.start.desc(), Job.type)
else: else:
query = query.order_by(Job.start, Job.type_id_) query = query.order_by(Job.start, Job.type)
count = query.count() count = query.count()
if limit is not None: if limit is not None:
query = query.limit(limit) query = query.limit(limit)
@ -264,7 +264,7 @@ def get_jobs(user, start=None, end=None, limit=None, offset=None, descending=Non
def add_job(event, job_type, required_services, start, end=None, comment=None): def add_job(event, job_type, required_services, start, end=None, comment=None):
job = Job( job = Job(
required_services=required_services, required_services=required_services,
type=job_type, type_=job_type,
start=start, start=start,
end=end, end=end,
comment=comment, comment=comment,
@ -340,6 +340,12 @@ def get_invitation(id: int):
return inv return inv
def get_invitations(user: User):
return Invitation.query.filter(
(Invitation.invitee_ == user) | (Invitation.inviter_ == user) | (Invitation.transferee_ == user)
).all()
def cancel_invitation(inv: Invitation): def cancel_invitation(inv: Invitation):
db.session.delete(inv) db.session.delete(inv)
db.session.commit() db.session.commit()
@ -386,7 +392,7 @@ def respond_invitation(invite: Invitation, accepted=True):
) )
@scheduled(id='dev.flaschengeist.events.assign_backups', minutes=30) @scheduled(id="dev.flaschengeist.events.assign_backups", minutes=30)
def assign_backups(): def assign_backups():
now = datetime.now(tz=timezone.utc) now = datetime.now(tz=timezone.utc)
# now + backup_time + next cron tick # now + backup_time + next cron tick

View File

@ -62,17 +62,17 @@ class Job(db.Model, ModelSerializeMixin):
id: int = db.Column(Serial, primary_key=True) id: int = db.Column(Serial, primary_key=True)
start: datetime = db.Column(UtcDateTime, nullable=False) start: datetime = db.Column(UtcDateTime, nullable=False)
end: Optional[datetime] = db.Column(UtcDateTime) end: Optional[datetime] = db.Column(UtcDateTime)
type: Union[JobType, int] = db.relationship("JobType")
comment: Optional[str] = db.Column(db.String(256)) comment: Optional[str] = db.Column(db.String(256))
type: int = db.Column("type_id", Serial, db.ForeignKey(f"{_table_prefix_}job_type.id"), nullable=False)
locked: bool = db.Column(db.Boolean(), default=False, nullable=False) locked: bool = db.Column(db.Boolean(), default=False, nullable=False)
services: list[Service] = db.relationship( services: list[Service] = db.relationship(
"Service", back_populates="job_", cascade="save-update, merge, delete, delete-orphan" "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) required_services: float = db.Column(db.Numeric(precision=4, scale=2, asdecimal=False), nullable=False)
type_: JobType = db.relationship("JobType")
event_ = db.relationship("Event", back_populates="jobs") event_ = db.relationship("Event", back_populates="jobs")
event_id_ = db.Column("event_id", Serial, db.ForeignKey(f"{_table_prefix_}event.id"), nullable=False) 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_") invitations_ = db.relationship("Invitation", cascade="all,delete,delete-orphan", back_populates="job_")
@ -112,6 +112,7 @@ class Invitation(db.Model, ModelSerializeMixin):
__tablename__ = _table_prefix_ + "invitation" __tablename__ = _table_prefix_ + "invitation"
id: int = db.Column(Serial, primary_key=True) id: int = db.Column(Serial, primary_key=True)
time: datetime = db.Column(UtcDateTime, nullable=False, default=UtcDateTime.current_utc)
job_id: int = db.Column(Serial, db.ForeignKey(_table_prefix_ + "job.id"), nullable=False) job_id: int = db.Column(Serial, db.ForeignKey(_table_prefix_ + "job.id"), nullable=False)
# Dummy properties for API export # Dummy properties for API export
invitee_id: str = None # User who was invited to take over invitee_id: str = None # User who was invited to take over

View File

@ -203,9 +203,7 @@ def _add_job(event, data):
start = from_iso_format(data["start"]) start = from_iso_format(data["start"])
end = dict_get(data, "end", None, type=from_iso_format) end = dict_get(data, "end", None, type=from_iso_format)
required_services = data["required_services"] required_services = data["required_services"]
job_type = data["type"] job_type = int(data["type"])
if isinstance(job_type, dict):
job_type = data["type"]["id"]
except (KeyError, ValueError): except (KeyError, ValueError):
raise BadRequest("Missing or invalid POST parameter") raise BadRequest("Missing or invalid POST parameter")
@ -239,22 +237,15 @@ def create_event(current_session):
try: try:
start = from_iso_format(data["start"]) start = from_iso_format(data["start"])
end = dict_get(data, "end", None, type=from_iso_format) end = dict_get(data, "end", None, type=from_iso_format)
data_type = data["type"] event_type = event_controller.get_event_type(int(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( event = event_controller.create_event(
start=start, start=start,
end=end, end=end,
name=dict_get(data, "name", None), name=dict_get(data, "name", None, type=str),
is_template=dict_get(data, "is_template", None), is_template=dict_get(data, "is_template", None, type=bool),
event_type=event_type, event_type=event_type,
description=dict_get(data, "description", None), description=dict_get(data, "description", None, type=str),
) )
if "jobs" in data: if "jobs" in data:
for job in data["jobs"]: for job in data["jobs"]:
@ -262,6 +253,11 @@ def create_event(current_session):
return jsonify(event) return jsonify(event)
except KeyError:
raise BadRequest("Missing POST parameter")
except (NotFound, ValueError):
raise BadRequest("Invalid parameter")
@EventPlugin.blueprint.route("/events/<int:event_id>", methods=["PUT"]) @EventPlugin.blueprint.route("/events/<int:event_id>", methods=["PUT"])
@login_required(permission=permissions.EDIT) @login_required(permission=permissions.EDIT)
@ -378,8 +374,8 @@ def update_job(event_id, job_id, current_session: Session):
job = event_controller.get_job(job_id, event_id) job = event_controller.get_job(job_id, event_id)
try: try:
if "type" in data: if "type" in data or "type_id" in data:
job.type = event_controller.get_job_type(data["type"]) job.type_ = event_controller.get_job_type(data.get("type", None) or data["type_id"])
job.start = from_iso_format(data.get("start", job.start)) job.start = from_iso_format(data.get("start", job.start))
job.end = from_iso_format(data.get("end", job.end)) job.end = from_iso_format(data.get("end", job.end))
job.comment = str(data.get("comment", job.comment)) job.comment = str(data.get("comment", job.comment))
@ -401,6 +397,12 @@ def get_jobs(current_session: Session):
return jsonify({"count": count, "result": result}) return jsonify({"count": count, "result": result})
@EventPlugin.blueprint.route("/events/jobs/<int:job_id>", methods=["GET"])
@login_required()
def get_job(job_id, current_session: Session):
return jsonify(event_controller.get_job(job_id))
@EventPlugin.blueprint.route("/events/jobs/<int:job_id>/assign", methods=["POST"]) @EventPlugin.blueprint.route("/events/jobs/<int:job_id>/assign", methods=["POST"])
@login_required() @login_required()
def assign_job(job_id, current_session: Session): def assign_job(job_id, current_session: Session):
@ -492,7 +494,9 @@ def invite(current_session: Session):
raise BadRequest raise BadRequest
return jsonify( return jsonify(
[ [
event_controller.invite(job, invitee, current_session.user_, userController.get_user(transferee) if transferee else None) event_controller.invite(
job, invitee, current_session.user_, userController.get_user(transferee) if transferee else None
)
for invitee in [userController.get_user(uid) for uid in data["invitees"]] for invitee in [userController.get_user(uid) for uid in data["invitees"]]
] ]
) )
@ -500,12 +504,18 @@ def invite(current_session: Session):
raise BadRequest raise BadRequest
@EventPlugin.blueprint.route("/events/invitations", methods=["GET"])
@login_required()
def get_invitations(current_session: Session):
return jsonify(event_controller.get_invitations(current_session.user_))
@EventPlugin.blueprint.route("/events/invitations/<int:invitation_id>", methods=["GET"]) @EventPlugin.blueprint.route("/events/invitations/<int:invitation_id>", methods=["GET"])
@login_required() @login_required()
def get_invitation(invitation_id: int, current_session: Session): def get_invitation(invitation_id: int, current_session: Session):
inv = event_controller.get_invitation(invitation_id) inv = event_controller.get_invitation(invitation_id)
if current_session.userid not in [inv.invitee_id, inv.inviter_id, inv.transferee_id]: if current_session.userid not in [inv.invitee_id, inv.inviter_id, inv.transferee_id]:
raise Forbidden raise NotFound
return jsonify(inv) return jsonify(inv)

View File

@ -20,6 +20,7 @@ classifiers =
universal = True universal = True
[options] [options]
python_requires = >=3.8
packages = packages =
flaschengeist_events flaschengeist_events
install_requires = install_requires =

2
src/api.d.ts vendored
View File

@ -15,11 +15,11 @@ declare namespace FG {
} }
interface Invitation { interface Invitation {
id: number; id: number;
time: Date;
job_id: number; job_id: number;
invitee_id: string; invitee_id: string;
inviter_id: string; inviter_id: string;
transferee_id?: string; transferee_id?: string;
transferee: User;
} }
interface Job { interface Job {
id: number; id: number;

View File

@ -22,6 +22,16 @@ export const innerRoutes: FG_Plugin.MenuRoute[] = [
component: () => import('../pages/EventOverview.vue'), component: () => import('../pages/EventOverview.vue'),
}, },
}, },
{
title: 'Dienstanfragen',
icon: 'mdi-account-switch',
shortcut: false,
route: {
path: 'events-requests',
name: 'events-requests',
component: () => import('../pages/EventRequests.vue'),
},
},
{ {
title: 'Dienstverwaltung', title: 'Dienstverwaltung',
icon: 'mdi-account-details', icon: 'mdi-account-details',
@ -34,16 +44,6 @@ export const innerRoutes: FG_Plugin.MenuRoute[] = [
props: (route) => ({ date: route.query.date }), props: (route) => ({ date: route.query.date }),
}, },
}, },
{
title: 'Dienstanfragen',
icon: 'mdi-account-switch',
shortcut: false,
route: {
path: 'events-requests',
name: 'events-requests',
component: () => import('../pages/EventRequests.vue'),
},
},
], ],
}, },
]; ];

View File

@ -28,6 +28,7 @@ export const useEventStore = defineStore({
jobTypes: [] as FG.JobType[], jobTypes: [] as FG.JobType[],
eventTypes: [] as FG.EventType[], eventTypes: [] as FG.EventType[],
templates: [] as FG.Event[], templates: [] as FG.Event[],
invitations: [] as FG.Invitation[],
}), }),
getters: {}, getters: {},
@ -168,6 +169,10 @@ export const useEventStore = defineStore({
.then(({ data }) => fixJob(data)); .then(({ data }) => fixJob(data));
}, },
async getJob(id: number) {
return api.get<FG.Job>(`/events/jobs/${id}`).then(({ data }) => fixJob(data));
},
async getJobs(filter?: FG.PaginationFilter) { async getJobs(filter?: FG.PaginationFilter) {
return api return api
.get<FG.PaginationResponse<FG.Job>>('/events/jobs', { params: <unknown>filter }) .get<FG.PaginationResponse<FG.Job>>('/events/jobs', { params: <unknown>filter })
@ -191,6 +196,14 @@ export const useEventStore = defineStore({
}); });
}, },
async getInvitations(force = false) {
if (this.invitations.length == 0 || force) {
const { data } = await api.get<FG.Invitation[]>('/events/invitations');
this.invitations = data;
}
return this.invitations;
},
async rejectInvitation(invite: FG.Invitation | number) { async rejectInvitation(invite: FG.Invitation | number) {
return api.delete(`/events/invitations/${typeof invite === 'number' ? invite : invite.id}`); return api.delete(`/events/invitations/${typeof invite === 'number' ? invite : invite.id}`);
}, },