Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,11 @@ web_modules/
config.yaml
*.db

# GimVicUrnik Firebase credentials
firebasecredentials.json

# Firebase service worker
firebase-messaging-sw.js

# Do not ignore .gitkeep files
!.gitkeep
4 changes: 4 additions & 0 deletions API/gimvicurnik/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
FeedHandler,
ListHandler,
MenusHandler,
NotificationsHandler,
ScheduleHandler,
SubstitutionsHandler,
TimetableHandler,
Expand All @@ -27,6 +28,7 @@
update_solsis_command,
cleanup_database_command,
update_timetable_command,
handle_notifications_command,
)
from .config import Config
from .database import Session, SessionFactory
Expand Down Expand Up @@ -250,6 +252,7 @@ def register_commands(self) -> None:
self.app.cli.add_command(update_solsis_command)
self.app.cli.add_command(cleanup_database_command)
self.app.cli.add_command(create_database_command)
self.app.cli.add_command(handle_notifications_command)

def register_routes(self) -> None:
"""Register all application routes."""
Expand All @@ -258,6 +261,7 @@ def register_routes(self) -> None:
TimetableHandler.register(self.app, self.config)
SubstitutionsHandler.register(self.app, self.config)
MenusHandler.register(self.app, self.config)
NotificationsHandler.register(self.app, self.config)
ScheduleHandler.register(self.app, self.config)
DocumentsHandler.register(self.app, self.config)
FeedHandler.register(self.app, self.config)
Expand Down
2 changes: 2 additions & 0 deletions API/gimvicurnik/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def _get_version(ctx: click.Context, _param: str, value: str) -> None:
pdfminer_version = metadata.version("pdfminer.six")
openpyxl_version = metadata.version("openpyxl")
mammoth_version = metadata.version("mammoth")
firebase_admin_version = metadata.version("firebase-admin")

try:
sentry_version = metadata.version("sentry-sdk")
Expand All @@ -61,6 +62,7 @@ def _get_version(ctx: click.Context, _param: str, value: str) -> None:
f"pdfminer: {pdfminer_version}\n"
f"openpyxl: {openpyxl_version}\n"
f"mammoth: {mammoth_version}\n"
f"firebase admin: {firebase_admin_version}\n"
f"Sentry SDK: {sentry_version}",
color=ctx.color,
)
Expand Down
1 change: 1 addition & 0 deletions API/gimvicurnik/blueprints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .feed import FeedHandler
from .list import ListHandler
from .menus import MenusHandler
from .notifications import NotificationsHandler
from .schedule import ScheduleHandler
from .substitutions import SubstitutionsHandler
from .timetable import TimetableHandler
43 changes: 43 additions & 0 deletions API/gimvicurnik/blueprints/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from __future__ import annotations

import typing

from .base import BaseHandler
from ..database import Notification, Session

if typing.TYPE_CHECKING:
from typing import Any
from flask import Blueprint
from ..config import Config


class NotificationsHandler(BaseHandler):
name = "notifications"

@classmethod
def routes(cls, bp: Blueprint, config: Config) -> None:
@bp.route("/notifications")
def get_notifications() -> list[dict[str, Any]]:
# fmt: off
query = (
Session.query(
Notification.date,
Notification.title,
Notification.content,
Notification.visible
)
.order_by(
Notification.date,
)
)
# fmt: on

return [
{
"date": notification.date.isoformat(),
"title": notification.title,
"content": notification.content,
}
for notification in query
if notification.visible
]
44 changes: 44 additions & 0 deletions API/gimvicurnik/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from ..database import Base, SessionFactory, Document, DocumentType
from ..updaters import EClassroomUpdater, MenuUpdater, TimetableUpdater, SolsisUpdater
from ..notifications import PushNotificationsHandler
from ..utils.sentry import with_transaction

if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -122,3 +123,46 @@ def create_database_command(ctx: click.Context, recreate: bool) -> None:

logging.getLogger(__name__).info("Creating the database")
Base.metadata.create_all(gimvicurnik.engine)


@click.command("handle-notifications", help="Handle the notifications.")
@click.option("--immediate-substitutions", help="Send immediate substitutions.", is_flag=True)
@click.option("--scheduled-substitutions", help="Send scheduled substitutions.", is_flag=True)
@click.option("--circulars", help="Send circulars.", is_flag=True)
@click.option("--menu", help="Send menus.", is_flag=True)
@click.option("--cleanup-users", help="Cleanup probably inactive users from the store.", is_flag=True)
@with_transaction(name="handle-notifications", op="command")
def handle_notifications_command(
immediate_substitutions: bool,
scheduled_substitutions: bool,
circulars: bool,
menu: bool,
cleanup_users: bool,
) -> None:
"""Handle the notifications."""

logging.getLogger(__name__).info("Handling notifications")

with SessionFactory.begin() as session:
gimvicurnik: GimVicUrnik = current_app.config["GIMVICURNIK"]

notifications = PushNotificationsHandler(
gimvicurnik.config.firebase, gimvicurnik.config.urls, session
)

if not any([immediate_substitutions, scheduled_substitutions, circulars, menu, cleanup_users]):
notifications.send_immediate_substitutions_notifications()
notifications.send_scheduled_substitutions_notifications()
notifications.send_circulars_notifications()
notifications.send_menu_notifications()
else:
if immediate_substitutions:
notifications.send_immediate_substitutions_notifications()
if scheduled_substitutions:
notifications.send_scheduled_substitutions_notifications()
if circulars:
notifications.send_circulars_notifications()
if menu:
notifications.send_menu_notifications()
if cleanup_users:
notifications.cleanup_users()
15 changes: 15 additions & 0 deletions API/gimvicurnik/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ class ConfigURLs:
api: str


# --------- FIREBASE CONFIG ------


@define(kw_only=True)
class ConfigFirebase:
apiKey: str
authDomain: str
projectId: str
storageBucket: str
messagingSenderId: int
appId: str
vapidKey: str


# -------- SENTRY CONFIG ---------


Expand Down Expand Up @@ -115,6 +129,7 @@ class Config:
urls: ConfigURLs
database: str
cors: list[str] = Factory(list)
firebase: ConfigFirebase
sentry: ConfigSentry | None = None
logging: dict | str | None = field(default=None, converter=_identity_convertor)
lessonTimes: list[ConfigLessonTime]
13 changes: 13 additions & 0 deletions API/gimvicurnik/database/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,16 @@ class LunchMenu(Base):

normal: Mapped[text | None]
vegetarian: Mapped[text | None]


class Notification(Base):
__tablename__ = "notifications"

id: Mapped[intpk]

date: Mapped[date_] = mapped_column(index=True)

title: Mapped[text]
content: Mapped[longtext | None]

visible: Mapped[bool]
1 change: 1 addition & 0 deletions API/gimvicurnik/errors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
LunchScheduleFormatError,
)
from .menu import MenuApiError, MenuDateError, MenuFormatError
from .notifications import NotificationsFirestoreError
from .solsis import SolsisApiError
from .timetable import TimetableApiError
5 changes: 5 additions & 0 deletions API/gimvicurnik/errors/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .base import GimVicUrnikError


class NotificationsFirestoreError(GimVicUrnikError):
pass
Loading