diff --git a/apps/api/src/domains/admin/repository.ts b/apps/api/src/domains/admin/repository.ts index 7693ad0..4fda17f 100644 --- a/apps/api/src/domains/admin/repository.ts +++ b/apps/api/src/domains/admin/repository.ts @@ -40,6 +40,7 @@ const resources: Record = { quiz_answers: { read: ['view_users', 'view_activity'] }, sent_dares: { read: ['view_users', 'view_activity', 'view_chats'] }, dare_messages: { read: ['view_chats', 'view_activity'] }, + notification_preferences: { read: 'view_users', idColumn: 'user_id' }, }; function identifier(value: string): string { diff --git a/apps/api/test/admin.integration.test.ts b/apps/api/test/admin.integration.test.ts index bd4855d..be67b6c 100644 --- a/apps/api/test/admin.integration.test.ts +++ b/apps/api/test/admin.integration.test.ts @@ -196,6 +196,21 @@ describe.skipIf(!databaseUrl || !isLocal)('admin repository authorization + audi }])).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 () => { const tables = await pool.query<{ table_name: string }>( `select table_name from information_schema.tables where table_schema=current_schema()