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
165 changes: 165 additions & 0 deletions backend/schemata/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3975,6 +3975,94 @@
]
}
},
"/v1/notifications": {
"get": {
"tags": [
"notifications"
],
"summary": "Get Notifications",
"description": "Get all notifications for the logged in user",
"operationId": "get_notifications_v1_notifications_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationsResponse"
}
}
}
}
}
}
},
"/v1/notifications/unread-count": {
"get": {
"tags": [
"notifications"
],
"summary": "Get Unread Count",
"description": "Get the count of unread notifications for the logged in user",
"operationId": "get_unread_count_v1_notifications_unread_count_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "integer",
"title": "Response Get Unread Count V1 Notifications Unread Count Get"
}
}
}
}
}
}
},
"/v1/notifications/{notification_id}/read": {
"patch": {
"tags": [
"notifications"
],
"summary": "Mark Notification As Read",
"description": "Mark a notification as read",
"operationId": "mark_notification_as_read_v1_notifications__notification_id__read_patch",
"parameters": [
{
"name": "notification_id",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"title": "Notification Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NotificationResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/": {
"get": {
"summary": "Root",
Expand Down Expand Up @@ -5377,6 +5465,83 @@
],
"title": "MinimalIssueTestResultAttachmentRuleResponse"
},
"NotificationResponse": {
"properties": {
"id": {
"type": "integer",
"title": "Id"
},
"user_id": {
"type": "integer",
"title": "User Id"
},
"notification_type": {
"$ref": "#/components/schemas/NotificationType"
},
"target_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Target Url"
},
"created_at": {
"type": "string",
"format": "date-time",
"title": "Created At"
},
"dismissed_at": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "Dismissed At"
}
},
"type": "object",
"required": [
"id",
"user_id",
"notification_type",
"target_url",
"created_at",
"dismissed_at"
],
"title": "NotificationResponse"
},
"NotificationType": {
"type": "string",
"enum": [
"USER_ASSIGNED_ARTEFACT_REVIEW",
"USER_ASSIGNED_ENVIRONMENT_REVIEW"
],
"title": "NotificationType"
},
"NotificationsResponse": {
"properties": {
"notifications": {
"items": {
"$ref": "#/components/schemas/NotificationResponse"
},
"type": "array",
"title": "Notifications"
}
},
"type": "object",
"required": [
"notifications"
],
"title": "NotificationsResponse"
},
"PendingRerun": {
"properties": {
"test_execution_id": {
Expand Down
18 changes: 18 additions & 0 deletions backend/test_observer/controllers/notifications/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2024 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License version 3, as
# published by the Free Software Foundation.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-FileCopyrightText: Copyright 2024 Canonical Ltd.
# SPDX-License-Identifier: AGPL-3.0-only

from .notifications import router

__all__ = ["router"]
90 changes: 90 additions & 0 deletions backend/test_observer/controllers/notifications/notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright 2024 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License version 3, as
# published by the Free Software Foundation.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-FileCopyrightText: Copyright 2024 Canonical Ltd.
# SPDX-License-Identifier: AGPL-3.0-only

from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func, select
from sqlalchemy.orm import Session

from test_observer.controllers.notifications.models import (
NotificationResponse,
NotificationsResponse,
)
from test_observer.data_access.models import Notification, User
from test_observer.data_access.setup import get_db
from test_observer.users.user_injection import get_current_user

router = APIRouter(tags=["notifications"])


@router.get("", response_model=NotificationsResponse)
def get_notifications(
user: User | None = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get all notifications for the logged in user"""
if not user:
raise HTTPException(status_code=401, detail="Not authenticated")

notifications = db.scalars(
select(Notification)
.where(Notification.user_id == user.id)
.order_by(Notification.created_at.desc())
).all()

return NotificationsResponse(notifications=list(notifications))
Comment on lines +33 to +48


@router.get("/unread-count", response_model=int)
def get_unread_count(
user: User | None = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get the count of unread notifications for the logged in user"""
if not user:
raise HTTPException(status_code=401, detail="Not authenticated")

count = db.scalar(
select(func.count(Notification.id))
.where(Notification.user_id == user.id)
.where(Notification.dismissed_at.is_(None))
)

return count or 0


@router.patch("/{notification_id}/read", response_model=NotificationResponse)
def mark_notification_as_read(
notification_id: int,
user: User | None = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Mark a notification as read"""
if not user:
raise HTTPException(status_code=401, detail="Not authenticated")

notification = db.get(Notification, notification_id)
if not notification:
raise HTTPException(status_code=404, detail="Notification not found")

if notification.user_id != user.id:
raise HTTPException(status_code=403, detail="Not authorized to modify this notification")

Comment on lines +79 to +85
notification.dismissed_at = datetime.now()
db.commit()
db.refresh(notification)
Comment on lines +86 to +88

return notification
2 changes: 2 additions & 0 deletions backend/test_observer/controllers/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from . import (
auth,
environments,
notifications,
reports,
test_cases,
test_executions,
Expand Down Expand Up @@ -54,6 +55,7 @@
router.include_router(teams.router, prefix="/v1/teams")
router.include_router(permissions.router, prefix="/v1/permissions")
router.include_router(applications.router, prefix="/v1/applications")
router.include_router(notifications.router, prefix="/v1/notifications")
router.include_router(docs.router)


Expand Down
Loading
Loading