feat(admin-api): expose notification_preferences as read-only

Resolve the earlier open question by mapping the profile-settings
domain's notification toggles to the existing notification_preferences
table (primary key user_id, no sensitive columns). Read-only, gated on
view_users, with a test covering read access and rejected writes.
This commit is contained in:
jjbrunton committed 2026-08-30 19:04:54 +01:00
1 parent 77f2718fdd
commit 25b7c6238e
2 files changed
+16

No files matched your search

+1
View File
@@ -40,6 +40,7 @@ const resources: Record<string, ResourceRule> = {
quiz_answers: { read: ['view_users', 'view_activity'] }, quiz_answers: { read: ['view_users', 'view_activity'] },
sent_dares: { read: ['view_users', 'view_activity', 'view_chats'] }, sent_dares: { read: ['view_users', 'view_activity', 'view_chats'] },
dare_messages: { read: ['view_chats', 'view_activity'] }, dare_messages: { read: ['view_chats', 'view_activity'] },
notification_preferences: { read: 'view_users', idColumn: 'user_id' },
}; };
function identifier(value: string): string { function identifier(value: string): string {
+15
View File
@@ -196,6 +196,21 @@ describe.skipIf(!databaseUrl || !isLocal)('admin repository authorization + audi
}])).rejects.toMatchObject({ code: 'forbidden' }); }])).rejects.toMatchObject({ code: 'forbidden' });
}); });
it('reads notification preferences for user support visibility without granting write access', async () => {
await pool.query(
"insert into notification_preferences(user_id,matches_enabled,dares_enabled) values($1,true,false)",
[creatorUser],
);
const principal = await repository.principal(superUser);
const result = await repository.query(principal, 'notification_preferences', {
filters: [{ column: 'user_id', op: 'eq', value: creatorUser }],
});
expect(result).toMatchObject({ count: 1, rows: [expect.objectContaining({ matches_enabled: true, dares_enabled: false })] });
await expect(repository.insert(principal, 'notification_preferences', [{
user_id: moderatorUser,
}])).rejects.toMatchObject({ code: 'forbidden' });
});
it('creates all legacy admin and dare cutover tables with preserved foreign keys', async () => { it('creates all legacy admin and dare cutover tables with preserved foreign keys', async () => {
const tables = await pool.query<{ table_name: string }>( const tables = await pool.query<{ table_name: string }>(
`select table_name from information_schema.tables where table_schema=current_schema() `select table_name from information_schema.tables where table_schema=current_schema()