Skip to content

Commit ed1cf4b

Browse files
committed
Add notification methods to the storage interface
1 parent e3e3aa7 commit ed1cf4b

5 files changed

Lines changed: 146 additions & 1 deletion

File tree

atr/storage/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class ReadAsFoundationCommitter(ReadAsGeneralPublic):
8080
def __init__(self, read: Read, data: db.Session) -> None:
8181
# self.checks = readers.checks.FoundationCommitter(read, self, data)
8282
# self.releases = readers.releases.FoundationCommitter(read, self, data)
83+
self.notifications = readers.notifications.FoundationCommitter(read, self, data)
8384
self.tokens = readers.tokens.FoundationCommitter(read, self, data)
8485
self.user = readers.user.FoundationCommitter(read, self, data)
8586

@@ -149,6 +150,7 @@ def __init__(self, write: Write, data: db.Session):
149150
self.checks = writers.checks.FoundationCommitter(write, self, data)
150151
self.keys = writers.keys.FoundationCommitter(write, self, data)
151152
self.mail = writers.mail.FoundationCommitter(write, self, data)
153+
self.notifications = writers.notifications.FoundationCommitter(write, self, data)
152154
self.policy = writers.policy.FoundationCommitter(write, self, data)
153155
self.project = writers.project.FoundationCommitter(write, self, data)
154156
self.release = writers.release.FoundationCommitter(write, self, data)
@@ -254,6 +256,7 @@ def __init__(self, data: db.Session, asf_uid: str):
254256
raise AccessError("User service writes require an ASF UID", status=500)
255257
self.__data = data
256258
self.__asf_uid = asf_uid
259+
self.notifications = writers.notifications.UserService(self, data)
257260

258261
@property
259262
def asf_uid(self) -> str:

atr/storage/readers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
# under the License.
1717

1818
import atr.storage.readers.checks as checks
19+
import atr.storage.readers.notifications as notifications
1920
import atr.storage.readers.releases as releases
2021
import atr.storage.readers.tokens as tokens
2122
import atr.storage.readers.user as user
2223

23-
__all__ = ["checks", "releases", "tokens", "user"]
24+
__all__ = ["checks", "notifications", "releases", "tokens", "user"]
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from __future__ import annotations
19+
20+
import sqlmodel
21+
22+
import atr.db as db
23+
import atr.models.sql as sql
24+
import atr.storage as storage
25+
26+
27+
class FoundationCommitter:
28+
# We never usually need to authenticate to read, but we do for notifications
29+
# They're still public, but we need to show them to the correct user
30+
# TODO: We should probably move all interaction.py stuff to storage anyway
31+
32+
def __init__(self, read: storage.Read, read_as: storage.ReadAsFoundationCommitter, data: db.Session):
33+
self.__read = read
34+
self.__read_as = read_as
35+
self.__data = data
36+
self.__asf_uid = read.authorisation.asf_uid
37+
38+
async def pending(self, limit: int = 50) -> list[sql.Notification]:
39+
if self.__asf_uid is None:
40+
raise storage.AccessError("Not authorized", status=403)
41+
return await self.__list_for_uid(limit)
42+
43+
async def __list_for_uid(self, limit: int) -> list[sql.Notification]:
44+
via = sql.validate_instrumented_attribute
45+
stmt = (
46+
sqlmodel.select(sql.Notification)
47+
.where(sql.Notification.asf_uid == self.__asf_uid)
48+
.order_by(via(sql.Notification.created), via(sql.Notification.id))
49+
.limit(limit)
50+
)
51+
return await self.__data.query_all(stmt)

atr/storage/writers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import atr.storage.writers.distributions as distributions
2424
import atr.storage.writers.keys as keys
2525
import atr.storage.writers.mail as mail
26+
import atr.storage.writers.notifications as notifications
2627
import atr.storage.writers.policy as policy
2728
import atr.storage.writers.project as project
2829
import atr.storage.writers.release as release
@@ -41,6 +42,7 @@
4142
"distributions",
4243
"keys",
4344
"mail",
45+
"notifications",
4446
"policy",
4547
"project",
4648
"release",
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
from __future__ import annotations
19+
20+
from typing import Final
21+
22+
import sqlmodel
23+
24+
import atr.db as db
25+
import atr.models.sql as sql
26+
import atr.storage as storage
27+
28+
_MAX_MESSAGE_LENGTH: Final[int] = 1024
29+
30+
31+
class FoundationCommitter:
32+
def __init__(self, write: storage.Write, write_as: storage.WriteAsFoundationCommitter, data: db.Session):
33+
self.__write = write
34+
self.__write_as = write_as
35+
self.__data = data
36+
self.__asf_uid = write_as.asf_uid
37+
38+
async def dismiss(self, notification_id: int) -> bool:
39+
via = sql.validate_instrumented_attribute
40+
result = await self.__data.execute(
41+
sqlmodel.delete(sql.Notification).where(
42+
via(sql.Notification.id) == notification_id,
43+
via(sql.Notification.asf_uid) == self.__asf_uid,
44+
)
45+
)
46+
await self.__data.commit()
47+
dismissed = (getattr(result, "rowcount", 0) or 0) > 0
48+
if dismissed:
49+
self.__write_as.append_to_audit_log(
50+
asf_uid=self.__asf_uid,
51+
notification_id=notification_id,
52+
)
53+
return dismissed
54+
55+
56+
class UserService:
57+
def __init__(self, waus: storage.WriteAsUserService, data: db.Session):
58+
self.__waus = waus
59+
self.__data = data
60+
self.__asf_uid = waus.asf_uid
61+
62+
async def create(
63+
self,
64+
message: str,
65+
level: sql.NotificationLevel = sql.NotificationLevel.ERROR,
66+
) -> sql.Notification:
67+
notification = sql.Notification(
68+
asf_uid=self.__asf_uid,
69+
message=_normalised_message(message),
70+
level=level,
71+
)
72+
self.__data.add(notification)
73+
await self.__data.commit()
74+
self.__waus.append_to_audit_log(
75+
asf_uid=self.__asf_uid,
76+
notification_id=notification.id,
77+
level=level.value,
78+
)
79+
return notification
80+
81+
82+
def _normalised_message(message: str) -> str:
83+
message = " ".join(message.strip().split())
84+
if not message:
85+
raise ValueError("Notification message cannot be empty")
86+
if len(message) > _MAX_MESSAGE_LENGTH:
87+
return message[: _MAX_MESSAGE_LENGTH - 3] + "..."
88+
return message

0 commit comments

Comments
 (0)