Skip to content

Commit 7058601

Browse files
committed
[owl] Notification Scope to Audience Column Rename (#917)
* Renamed column * DB migration * Enum retype
1 parent 4c2ae8f commit 7058601

8 files changed

Lines changed: 95 additions & 72 deletions

File tree

clients/python/src/jamaibase/types/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@
8787
ModelProvider,
8888
ModelType,
8989
Notification_,
90+
NotificationAudience,
9091
NotificationCreate,
9192
NotificationGroup_,
9293
NotificationGroupCreate,
9394
NotificationGroupRead,
9495
NotificationRead,
95-
NotificationScope,
9696
NotificationType,
9797
OnPremProvider,
9898
Organization_,

clients/python/src/jamaibase/types/db.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,7 +1323,7 @@ class ProjectKeyRead(ProjectKey_):
13231323
pass
13241324

13251325

1326-
class NotificationScope(StrEnum):
1326+
class NotificationAudience(StrEnum):
13271327
SYSTEM = "SYSTEM"
13281328
ORGANIZATION = "ORGANIZATION"
13291329
PROJECT = "PROJECT"
@@ -1356,8 +1356,8 @@ class NotificationType(StrEnum):
13561356

13571357

13581358
class NotificationGroupCreate(_BaseModel):
1359-
scope: NotificationScope = Field(
1360-
description="Notification scope.",
1359+
audience: NotificationAudience = Field(
1360+
description="Notification audience.",
13611361
)
13621362
event_type: NotificationType = Field(
13631363
description="Notification event type.",

services/api/src/owl/db/__init__.py

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -654,49 +654,72 @@ async def _migrate_notification_schema(engine: AsyncEngine) -> bool:
654654
Migrate NotificationGroup and Notification tables:
655655
- Add `subject_id` (FK→User SET NULL) and `message` columns to NotificationGroup
656656
- Rename `body` → `message` in Notification
657+
- Rename `scope` → `audience` in NotificationGroup
658+
- Retype audience column from PG enum `notificationscope` to `notificationaudience`, drop old enum
657659
"""
658660
group_table = "NotificationGroup"
659661
notif_table = "Notification"
660-
661-
async with engine.connect() as conn:
662-
group_has_message = await _check_column_exists(conn, group_table, "message")
663-
group_has_subject = await _check_column_exists(conn, group_table, "subject_id")
664-
notif_has_message = await _check_column_exists(conn, notif_table, "message")
665-
666-
if group_has_message and group_has_subject and notif_has_message:
667-
return False
662+
migrated = False
668663

669664
async with engine.begin() as conn:
670-
if not group_has_subject:
665+
if not await _check_column_exists(conn, group_table, "subject_id"):
671666
await conn.execute(
672667
text(
673-
f"""
674-
ALTER TABLE {SCHEMA}."{group_table}"
675-
ADD COLUMN subject_id TEXT DEFAULT NULL
676-
REFERENCES {SCHEMA}."User"(id) ON DELETE SET NULL;
677-
"""
668+
f'ALTER TABLE {SCHEMA}."{group_table}" '
669+
f"ADD COLUMN subject_id TEXT DEFAULT NULL "
670+
f'REFERENCES {SCHEMA}."User"(id) ON DELETE SET NULL'
678671
)
679672
)
680-
if not group_has_message:
673+
migrated = True
674+
675+
if not await _check_column_exists(conn, group_table, "message"):
681676
await conn.execute(
682677
text(
683-
f"""
684-
ALTER TABLE {SCHEMA}."{group_table}"
685-
ADD COLUMN message TEXT NOT NULL DEFAULT '';
686-
"""
678+
f'ALTER TABLE {SCHEMA}."{group_table}" '
679+
f"ADD COLUMN message TEXT NOT NULL DEFAULT ''"
687680
)
688681
)
689-
if not notif_has_message:
682+
migrated = True
683+
684+
if await _check_column_exists(conn, notif_table, "body"):
685+
if not await _check_column_exists(conn, notif_table, "message"):
686+
await conn.execute(
687+
text(f'ALTER TABLE {SCHEMA}."{notif_table}" RENAME COLUMN body TO message')
688+
)
689+
migrated = True
690+
691+
if await _check_column_exists(conn, group_table, "scope"):
692+
if not await _check_column_exists(conn, group_table, "audience"):
693+
await conn.execute(
694+
text(f'ALTER TABLE {SCHEMA}."{group_table}" RENAME COLUMN scope TO audience')
695+
)
696+
migrated = True
697+
698+
# Retype audience column: PG enum notificationscope → notificationaudience
699+
old_enum_exists = (
690700
await conn.execute(
691701
text(
692-
f"""
693-
ALTER TABLE {SCHEMA}."{notif_table}"
694-
RENAME COLUMN body TO message;
695-
"""
702+
f"SELECT EXISTS (SELECT 1 FROM pg_type t "
703+
f"JOIN pg_namespace n ON n.oid = t.typnamespace "
704+
f"WHERE n.nspname = '{SCHEMA}' AND t.typname = 'notificationscope')"
696705
)
697706
)
698-
logger.info("Successfully migrated notification schema (subject_id, message, body→message).")
699-
return True
707+
).scalar()
708+
if old_enum_exists:
709+
await conn.execute(
710+
text(
711+
f'ALTER TABLE {SCHEMA}."{group_table}" '
712+
f"ALTER COLUMN audience TYPE {SCHEMA}.notificationaudience "
713+
f"USING audience::text::{SCHEMA}.notificationaudience"
714+
)
715+
)
716+
await conn.execute(text(f"DROP TYPE {SCHEMA}.notificationscope"))
717+
logger.info("Retyped audience column and dropped old PG enum notificationscope.")
718+
migrated = True
719+
720+
if migrated:
721+
logger.info("Successfully migrated notification schema.")
722+
return migrated
700723

701724

702725
async def migrate_db():

services/api/src/owl/db/models/oss.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
LanguageCodeList,
4949
ModelCapability,
5050
ModelType,
51-
NotificationScope,
51+
NotificationAudience,
5252
NotificationType,
5353
Page,
5454
PaymentState,
@@ -1597,8 +1597,8 @@ class NotificationGroup(_TableBase, table=True):
15971597
primary_key=True,
15981598
description="Notification group ID.",
15991599
)
1600-
scope: NotificationScope = SqlField(
1601-
description="Notification scope.",
1600+
audience: NotificationAudience = SqlField(
1601+
description="Notification audience.",
16021602
)
16031603
event_type: NotificationType = SqlField(
16041604
index=True,

services/api/src/owl/routers/notification.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ async def create_notification_group(
5353

5454
# Fan-out in background
5555
intent = NotificationIntent(
56-
scope=body.scope,
56+
audience=body.audience,
5757
event_type=body.event_type,
5858
message=body.message,
5959
actor_id=body.actor_id,

services/api/src/owl/types/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@
111111
MultiRowCompletionResponse,
112112
MultiRowDeleteRequest,
113113
Notification_,
114+
NotificationAudience,
114115
NotificationCreate,
115116
NotificationGroup_,
116117
NotificationGroupCreate,
117118
NotificationGroupRead,
118119
NotificationRead,
119-
NotificationScope,
120120
NotificationType,
121121
NullableStr,
122122
OkResponse,

services/api/src/owl/utils/notifications.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from owl.db import SCHEMA, async_session
99
from owl.db.models import NotificationGroup
10-
from owl.types import NotificationScope, NotificationType, ProductType, Role
10+
from owl.types import NotificationAudience, NotificationType, ProductType, Role
1111
from owl.utils.dates import now
1212

1313
# Mapping from ProductType to NotificationType for limit alerts
@@ -49,7 +49,7 @@
4949
class NotificationIntent:
5050
"""Plain data carrier for a notification to be dispatched in the background."""
5151

52-
scope: NotificationScope
52+
audience: NotificationAudience
5353
event_type: NotificationType
5454
message: str
5555
actor_id: str | None = None
@@ -65,7 +65,7 @@ async def _fan_out_notifications(
6565
*,
6666
group_id: str,
6767
message: str,
68-
scope: NotificationScope,
68+
audience: NotificationAudience,
6969
organization_id: str | None = None,
7070
project_id: str | None = None,
7171
recipient_ids: list[str] | None = None,
@@ -76,27 +76,27 @@ async def _fan_out_notifications(
7676
base_vals = ":group_id, :message, '{}'::jsonb, :ts, :ts"
7777
params: dict = dict(group_id=group_id, message=message, ts=now())
7878

79-
if scope == NotificationScope.ORGANIZATION:
79+
if audience == NotificationAudience.ORGANIZATION:
8080
where = "om.organization_id = :org_id"
8181
params["org_id"] = organization_id
8282
if notif_admin_only:
8383
where += " AND om.role = :role"
8484
params["role"] = Role.ADMIN.value
8585
sql = f'INSERT INTO {base_cols} SELECT om.user_id, {base_vals} FROM {SCHEMA}."OrgMember" om WHERE {where}'
8686

87-
elif scope == NotificationScope.PROJECT:
87+
elif audience == NotificationAudience.PROJECT:
8888
where = "pm.project_id = :proj_id"
8989
params["proj_id"] = project_id
9090
if notif_admin_only:
9191
where += " AND pm.role = :role"
9292
params["role"] = Role.ADMIN.value
9393
sql = f'INSERT INTO {base_cols} SELECT pm.user_id, {base_vals} FROM {SCHEMA}."ProjectMember" pm WHERE {where}'
9494

95-
elif scope == NotificationScope.USER:
95+
elif audience == NotificationAudience.USER:
9696
params["user_ids"] = recipient_ids or []
9797
sql = f'INSERT INTO {base_cols} SELECT u.id, {base_vals} FROM {SCHEMA}."User" u WHERE u.id = ANY(:user_ids)'
9898

99-
elif scope == NotificationScope.SYSTEM:
99+
elif audience == NotificationAudience.SYSTEM:
100100
sql = f'INSERT INTO {base_cols} SELECT u.id, {base_vals} FROM {SCHEMA}."User" u'
101101

102102
else:
@@ -115,7 +115,7 @@ async def dispatch_notification_intent(
115115
async with async_session() as session:
116116
if group_id is None:
117117
group = NotificationGroup(
118-
scope=intent.scope,
118+
audience=intent.audience,
119119
event_type=intent.event_type,
120120
organization_id=intent.organization_id,
121121
project_id=intent.project_id,
@@ -131,7 +131,7 @@ async def dispatch_notification_intent(
131131
session,
132132
group_id=group_id,
133133
message=intent.message,
134-
scope=intent.scope,
134+
audience=intent.audience,
135135
organization_id=intent.organization_id,
136136
project_id=intent.project_id,
137137
recipient_ids=intent.recipient_ids,
@@ -142,7 +142,7 @@ async def dispatch_notification_intent(
142142
if row_count == 0:
143143
logger.warning(
144144
f"No recipients resolved for notification {intent.event_type} "
145-
f"(scope={intent.scope}, org={intent.organization_id}, proj={intent.project_id})"
145+
f"(audience={intent.audience}, org={intent.organization_id}, proj={intent.project_id})"
146146
)
147147
else:
148148
logger.bind(
@@ -165,7 +165,7 @@ def notify_org_invitation(
165165
role: str,
166166
) -> NotificationIntent:
167167
return NotificationIntent(
168-
scope=NotificationScope.USER,
168+
audience=NotificationAudience.USER,
169169
event_type=NotificationType.ORG_INVITATION,
170170
message=f"**{actor_name}** invited you to join organization **{org_name}** with role **{role}**.",
171171
actor_id=actor_id,
@@ -186,7 +186,7 @@ def notify_project_invitation(
186186
role: str,
187187
) -> NotificationIntent:
188188
return NotificationIntent(
189-
scope=NotificationScope.USER,
189+
audience=NotificationAudience.USER,
190190
event_type=NotificationType.PROJECT_INVITATION,
191191
message=f"**{actor_name}** invited you to join project **{project_name}** with role **{role}**.",
192192
actor_id=actor_id,
@@ -206,7 +206,7 @@ def notify_org_invitation_revoked(
206206
org_name: str,
207207
) -> NotificationIntent:
208208
return NotificationIntent(
209-
scope=NotificationScope.USER,
209+
audience=NotificationAudience.USER,
210210
event_type=NotificationType.ORG_INVITATION_REVOKED,
211211
message=f"**{actor_name}** revoked your invitation to organization **{org_name}**.",
212212
actor_id=actor_id,
@@ -226,7 +226,7 @@ def notify_project_invitation_revoked(
226226
organization_id: str,
227227
) -> NotificationIntent:
228228
return NotificationIntent(
229-
scope=NotificationScope.USER,
229+
audience=NotificationAudience.USER,
230230
event_type=NotificationType.PROJECT_INVITATION_REVOKED,
231231
message=f"**{actor_name}** revoked your invitation to project **{project_name}**.",
232232
actor_id=actor_id,
@@ -245,7 +245,7 @@ def notify_org_member_joined(
245245
subject_name: str,
246246
) -> NotificationIntent:
247247
return NotificationIntent(
248-
scope=NotificationScope.ORGANIZATION,
248+
audience=NotificationAudience.ORGANIZATION,
249249
event_type=NotificationType.ORG_MEMBER_JOINED,
250250
message=f"**{subject_name}** joined organization **{org_name}**.",
251251
subject_id=subject_id,
@@ -262,7 +262,7 @@ def notify_project_member_joined(
262262
subject_name: str,
263263
) -> NotificationIntent:
264264
return NotificationIntent(
265-
scope=NotificationScope.PROJECT,
265+
audience=NotificationAudience.PROJECT,
266266
event_type=NotificationType.PROJECT_MEMBER_JOINED,
267267
message=f"**{subject_name}** joined project **{project_name}**.",
268268
subject_id=subject_id,
@@ -281,7 +281,7 @@ def notify_org_role_updated(
281281
role: str,
282282
) -> NotificationIntent:
283283
return NotificationIntent(
284-
scope=NotificationScope.USER,
284+
audience=NotificationAudience.USER,
285285
event_type=NotificationType.ORG_ROLE_UPDATED,
286286
message=f"**{actor_name}** changed your role to **{role}** in organization **{org_name}**.",
287287
actor_id=actor_id,
@@ -302,7 +302,7 @@ def notify_project_role_updated(
302302
role: str,
303303
) -> NotificationIntent:
304304
return NotificationIntent(
305-
scope=NotificationScope.USER,
305+
audience=NotificationAudience.USER,
306306
event_type=NotificationType.PROJECT_ROLE_UPDATED,
307307
message=f"**{actor_name}** changed your role to **{role}** in project **{project_name}**.",
308308
actor_id=actor_id,
@@ -323,7 +323,7 @@ def notify_org_owner_updated(
323323
subject_name: str,
324324
) -> NotificationIntent:
325325
return NotificationIntent(
326-
scope=NotificationScope.ORGANIZATION,
326+
audience=NotificationAudience.ORGANIZATION,
327327
event_type=NotificationType.ORG_OWNER_UPDATED,
328328
message=f"**{actor_name}** transferred ownership of organization **{org_name}** to **{subject_name}**.",
329329
actor_id=actor_id,
@@ -343,7 +343,7 @@ def notify_project_owner_updated(
343343
subject_name: str,
344344
) -> NotificationIntent:
345345
return NotificationIntent(
346-
scope=NotificationScope.PROJECT,
346+
audience=NotificationAudience.PROJECT,
347347
event_type=NotificationType.PROJECT_OWNER_UPDATED,
348348
message=f"**{actor_name}** transferred ownership of project **{project_name}** to **{subject_name}**.",
349349
actor_id=actor_id,
@@ -365,7 +365,7 @@ def notify_quota_limit(
365365
event_type = _PRODUCT_TO_NOTIFICATION_TYPE[product_type]
366366
label = _PRODUCT_LABEL[product_type]
367367
return NotificationIntent(
368-
scope=NotificationScope.ORGANIZATION,
368+
audience=NotificationAudience.ORGANIZATION,
369369
event_type=event_type,
370370
message=f"{label} usage has reached **{threshold}%** of quota ({usage:,.2f}/{quota:,.2f} {unit}).",
371371
organization_id=organization_id,

0 commit comments

Comments
 (0)