Skip to content

Commit

Permalink
feat: Rework notice integration
Browse files Browse the repository at this point in the history
  • Loading branch information
zusorio committed Feb 10, 2025
1 parent 0054ca0 commit fae1700
Show file tree
Hide file tree
Showing 73 changed files with 1,439 additions and 804 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0

"""Make announcements dismissible
Revision ID: ca9ce61491a7
Revises: 8731ac0b284e
Create Date: 2025-02-07 19:42:01.723097
"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "ca9ce61491a7"
down_revision = "8731ac0b284e"
branch_labels = None
depends_on = None


def upgrade():
op.rename_table("notices", "announcements")

op.add_column(
"announcements", sa.Column("dismissible", sa.Boolean(), nullable=True)
)
op.execute("UPDATE announcements SET dismissible = false")
op.alter_column("announcements", "dismissible", nullable=False)
File renamed without changes.
53 changes: 53 additions & 0 deletions backend/capellacollab/announcements/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0


from collections import abc

import sqlalchemy as sa
from sqlalchemy import orm

from capellacollab.announcements import models


def get_announcements(
db: orm.Session,
) -> abc.Sequence[models.DatabaseAnnouncement]:
return db.execute(sa.select(models.DatabaseAnnouncement)).scalars().all()


def get_announcement_by_id(
db: orm.Session, announcement_id: int
) -> models.DatabaseAnnouncement | None:
return db.execute(
sa.select(models.DatabaseAnnouncement).where(
models.DatabaseAnnouncement.id == announcement_id
)
).scalar_one_or_none()


def create_announcement(
db: orm.Session, body: models.CreateAnnouncementRequest
) -> models.DatabaseAnnouncement:
announcement = models.DatabaseAnnouncement(**body.model_dump())
db.add(announcement)
db.commit()
return announcement


def update_announcement(
db: orm.Session,
announcement: models.DatabaseAnnouncement,
body: models.CreateAnnouncementRequest,
) -> models.DatabaseAnnouncement:
for field, value in body.model_dump().items():
setattr(announcement, field, value)
db.commit()
return announcement


def delete_announcement(
db: orm.Session, announcement: models.DatabaseAnnouncement
) -> None:
db.delete(announcement)
db.commit()
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@


class AnnouncementNotFoundError(core_exceptions.BaseError):
def __init__(self, notice_id: int):
def __init__(self, announcement_id: int):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
err_code="ANNOUNCEMENT_NOT_FOUND",
title="Announcement not found",
reason=f"The announcement with ID {notice_id} doesn't exist",
reason=f"The announcement with ID {announcement_id} doesn't exist",
)

@classmethod
Expand Down
20 changes: 20 additions & 0 deletions backend/capellacollab/announcements/injectables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0

import fastapi
from sqlalchemy import orm

from capellacollab.announcements import crud, models
from capellacollab.core import database

from . import exceptions


def get_existing_announcement(
announcement_id: int,
db: orm.Session = fastapi.Depends(database.get_db),
) -> models.DatabaseAnnouncement:
if announcement := crud.get_announcement_by_id(db, announcement_id):
return announcement

raise exceptions.AnnouncementNotFoundError(announcement_id)
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from capellacollab.core import pydantic as core_pydantic


class NoticeLevel(enum.Enum):
class AnnouncementLevel(enum.Enum):
PRIMARY = "primary"
SECONDARY = "secondary"
SUCCESS = "success"
Expand All @@ -19,22 +19,24 @@ class NoticeLevel(enum.Enum):
ALERT = "alert"


class CreateNoticeRequest(core_pydantic.BaseModel):
level: NoticeLevel
class CreateAnnouncementRequest(core_pydantic.BaseModel):
level: AnnouncementLevel
title: str
message: str
dismissible: bool


class NoticeResponse(CreateNoticeRequest):
class AnnouncementResponse(CreateAnnouncementRequest):
id: int


class DatabaseNotice(database.Base):
__tablename__ = "notices"
class DatabaseAnnouncement(database.Base):
__tablename__ = "announcements"

id: orm.Mapped[int] = orm.mapped_column(
init=False, primary_key=True, index=True
)
title: orm.Mapped[str]
message: orm.Mapped[str]
level: orm.Mapped[NoticeLevel]
level: orm.Mapped[AnnouncementLevel]
dismissible: orm.Mapped[bool] = orm.mapped_column(default=False)
99 changes: 99 additions & 0 deletions backend/capellacollab/announcements/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0

import fastapi
from sqlalchemy import orm

from capellacollab.core import database
from capellacollab.permissions import injectables as permissions_injectables
from capellacollab.permissions import models as permissions_models

from . import crud, injectables, models

router = fastapi.APIRouter()


@router.get(
"",
response_model=list[models.AnnouncementResponse],
)
def get_announcements(db: orm.Session = fastapi.Depends(database.get_db)):
return crud.get_announcements(db)


@router.get("/{announcement_id}")
def get_announcement_by_id(
announcement: models.DatabaseAnnouncement = fastapi.Depends(
injectables.get_existing_announcement
),
):
return announcement


@router.post(
"",
dependencies=[
fastapi.Depends(
permissions_injectables.PermissionValidation(
required_scope=permissions_models.GlobalScopes(
admin=permissions_models.AdminScopes(
announcements={permissions_models.UserTokenVerb.CREATE}
)
)
),
)
],
)
def create_announcement(
post_announcement: models.CreateAnnouncementRequest,
db: orm.Session = fastapi.Depends(database.get_db),
):
return crud.create_announcement(db, post_announcement)


@router.patch(
"/{announcement_id}",
dependencies=[
fastapi.Depends(
permissions_injectables.PermissionValidation(
required_scope=permissions_models.GlobalScopes(
admin=permissions_models.AdminScopes(
announcements={permissions_models.UserTokenVerb.UPDATE}
)
)
),
)
],
)
def update_announcement(
post_announcement: models.CreateAnnouncementRequest,
announcement: models.DatabaseAnnouncement = fastapi.Depends(
injectables.get_existing_announcement
),
db: orm.Session = fastapi.Depends(database.get_db),
):
return crud.update_announcement(db, announcement, post_announcement)


@router.delete(
"/{announcement_id}",
status_code=204,
dependencies=[
fastapi.Depends(
permissions_injectables.PermissionValidation(
required_scope=permissions_models.GlobalScopes(
admin=permissions_models.AdminScopes(
announcements={permissions_models.UserTokenVerb.DELETE}
)
)
),
)
],
)
def delete_announcement(
announcement: models.DatabaseAnnouncement = fastapi.Depends(
injectables.get_existing_announcement
),
db: orm.Session = fastapi.Depends(database.get_db),
):
crud.delete_announcement(db, announcement)
16 changes: 16 additions & 0 deletions backend/capellacollab/core/database/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from sqlalchemy import orm

from capellacollab import core
from capellacollab.announcements import crud as announcements_crud
from capellacollab.announcements import models as announcements_models
from capellacollab.configuration.app import config
from capellacollab.core import database
from capellacollab.events import crud as events_crud
Expand Down Expand Up @@ -82,6 +84,7 @@ def migrate_db(engine, database_url: str):
LOGGER.info("Database structure creation successful")
command.stamp(alembic_cfg, "head")
initialize_admin_user(session)
create_welcome_announcement(session)
create_tools(session)

initialize_capellambse_test_project(session)
Expand All @@ -105,6 +108,19 @@ def initialize_admin_user(db: orm.Session):
LOGGER.info("Initialized admin user %s", config.initial.admin)


def create_welcome_announcement(db: orm.Session):
welcome_announcement = announcements_crud.create_announcement(
db,
announcements_models.CreateAnnouncementRequest(
title="Welcome to the Capella Collaboration Manager",
message="Make sure to check out our documentation to learn more",
level=announcements_models.AnnouncementLevel.PRIMARY,
dismissible=True,
),
)
LOGGER.info("Initialized welcome announcement %s", welcome_announcement.id)


def initialize_capellambse_test_project(db: orm.Session):
project = projects_crud.create_project(
db=db,
Expand Down
2 changes: 1 addition & 1 deletion backend/capellacollab/core/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
# pylint: disable=unused-import
# These import statements of the models are required and should not be removed! (SQLAlchemy will not load the models otherwise)

import capellacollab.announcements.models
import capellacollab.configuration.models
import capellacollab.events.models
import capellacollab.feedback.models
import capellacollab.notices.models
import capellacollab.projects.models
import capellacollab.projects.permissions.models
import capellacollab.projects.toolmodels.backups.models
Expand Down
38 changes: 0 additions & 38 deletions backend/capellacollab/notices/crud.py

This file was deleted.

20 changes: 0 additions & 20 deletions backend/capellacollab/notices/injectables.py

This file was deleted.

Loading

0 comments on commit fae1700

Please sign in to comment.