(
user: ContentUser,
opts: { committeeSlug?: string } = {}
)
| 681 | * Admins see all pending content; committee leads see only their committees' pending items. |
| 682 | */ |
| 683 | export async function listPendingContentForUser( |
| 684 | user: ContentUser, |
| 685 | opts: { committeeSlug?: string } = {} |
| 686 | ): Promise<PendingContentResult> { |
| 687 | const pool = getPool(); |
| 688 | const { committeeSlug } = opts; |
| 689 | |
| 690 | // Committees this user leads (direct workos_user_id or via slack mapping) |
| 691 | const leaderResult = await pool.query( |
| 692 | `SELECT wg.id |
| 693 | FROM working_group_leaders wgl |
| 694 | LEFT JOIN slack_user_mappings sm ON wgl.user_id = sm.slack_user_id AND sm.workos_user_id IS NOT NULL |
| 695 | JOIN working_groups wg ON wg.id = wgl.working_group_id |
| 696 | WHERE wgl.user_id = $1 OR sm.workos_user_id = $1`, |
| 697 | [user.id] |
| 698 | ); |
| 699 | const ledCommitteeIds = leaderResult.rows.map(c => c.id); |
| 700 | const userIsAdmin = await isWebUserAAOAdmin(user.id); |
| 701 | |
| 702 | if (!userIsAdmin && ledCommitteeIds.length === 0) { |
| 703 | return { items: [], summary: { total: 0, by_collection: {} } }; |
| 704 | } |
| 705 | |
| 706 | let query = ` |
| 707 | SELECT |
| 708 | p.id, p.title, p.subtitle, p.excerpt, p.content, p.slug, p.content_type, |
| 709 | p.external_url, p.external_site_name, |
| 710 | p.proposer_user_id, p.proposed_at, p.working_group_id, |
| 711 | wg.name as committee_name, wg.slug as committee_slug, |
| 712 | u.first_name, u.last_name, u.email as proposer_email, |
| 713 | (SELECT json_agg(json_build_object( |
| 714 | 'user_id', ca.user_id, |
| 715 | 'display_name', ca.display_name |
| 716 | ) ORDER BY ca.display_order) |
| 717 | FROM content_authors ca WHERE ca.perspective_id = p.id) as authors |
| 718 | FROM perspectives p |
| 719 | LEFT JOIN working_groups wg ON wg.id = p.working_group_id |
| 720 | LEFT JOIN users u ON u.workos_user_id = p.proposer_user_id |
| 721 | WHERE p.status = 'pending_review' |
| 722 | `; |
| 723 | const params: (string | string[])[] = []; |
| 724 | |
| 725 | if (!userIsAdmin) { |
| 726 | params.push(ledCommitteeIds); |
| 727 | query += ` AND p.working_group_id = ANY($${params.length})`; |
| 728 | } |
| 729 | if (committeeSlug) { |
| 730 | params.push(committeeSlug); |
| 731 | query += ` AND wg.slug = $${params.length}`; |
| 732 | } |
| 733 | query += ` ORDER BY p.proposed_at ASC`; |
| 734 | |
| 735 | const result = await pool.query(query, params); |
| 736 | |
| 737 | const items: PendingContentItem[] = result.rows.map(row => ({ |
| 738 | id: row.id, |
| 739 | title: row.title, |
| 740 | subtitle: row.subtitle, |
no test coverage detected