()
| 1380 | * Returns a router to be mounted at /api/me/content |
| 1381 | */ |
| 1382 | export function createMyContentRouter(): Router { |
| 1383 | const router = Router(); |
| 1384 | |
| 1385 | // GET /api/me/content - Get all content where user has a relationship |
| 1386 | router.get('/', requireAuth, async (req, res) => { |
| 1387 | try { |
| 1388 | const user = req.user!; |
| 1389 | const status = req.query.status as string | undefined; |
| 1390 | const collection = req.query.collection as string | undefined; |
| 1391 | const relationship = req.query.relationship as string | undefined; |
| 1392 | const limit = Math.min(parseInt(req.query.limit as string) || 50, 100); |
| 1393 | const pool = getPool(); |
| 1394 | |
| 1395 | // Get committees user leads (for "owner" relationship) |
| 1396 | const leaderResult = await pool.query( |
| 1397 | `SELECT working_group_id FROM working_group_leaders WHERE user_id = $1`, |
| 1398 | [user.id] |
| 1399 | ); |
| 1400 | const ledCommitteeIds = leaderResult.rows.map(r => r.working_group_id); |
| 1401 | |
| 1402 | // Admins can see every perspective here so they can edit anything |
| 1403 | // (including content that predates them, has no proposer, or belongs to a |
| 1404 | // committee they don't lead). Relationships are still computed so the UI |
| 1405 | // can still distinguish their own contributions. |
| 1406 | const userIsAdmin = await isWebUserAAOAdmin(user.id); |
| 1407 | |
| 1408 | // Build the query |
| 1409 | let query = ` |
| 1410 | SELECT DISTINCT ON (p.id) |
| 1411 | p.id, p.slug, p.content_type, p.title, p.subtitle, p.category, p.excerpt, |
| 1412 | p.content, p.tags, p.featured_image_url, |
| 1413 | p.external_url, p.external_site_name, p.status, p.published_at, |
| 1414 | p.created_at, p.updated_at, p.working_group_id, p.proposer_user_id, |
| 1415 | wg.name as committee_name, wg.slug as committee_slug, |
| 1416 | -- Determine relationships |
| 1417 | CASE WHEN p.proposer_user_id = $1 THEN true ELSE false END as is_proposer, |
| 1418 | CASE WHEN EXISTS (SELECT 1 FROM content_authors ca WHERE ca.perspective_id = p.id AND ca.user_id = $1) THEN true ELSE false END as is_author, |
| 1419 | CASE WHEN p.working_group_id = ANY($2) THEN true ELSE false END as is_lead, |
| 1420 | -- Get authors |
| 1421 | (SELECT json_agg(json_build_object( |
| 1422 | 'user_id', ca.user_id, |
| 1423 | 'display_name', ca.display_name, |
| 1424 | 'display_title', ca.display_title |
| 1425 | ) ORDER BY ca.display_order) |
| 1426 | FROM content_authors ca WHERE ca.perspective_id = p.id) as authors |
| 1427 | FROM perspectives p |
| 1428 | LEFT JOIN working_groups wg ON wg.id = p.working_group_id |
| 1429 | WHERE ( |
| 1430 | $3::boolean = true |
| 1431 | OR p.proposer_user_id = $1 |
| 1432 | OR EXISTS (SELECT 1 FROM content_authors ca WHERE ca.perspective_id = p.id AND ca.user_id = $1) |
| 1433 | OR p.working_group_id = ANY($2) |
| 1434 | ) |
| 1435 | `; |
| 1436 | const params: (string | string[] | number | boolean)[] = [user.id, ledCommitteeIds, userIsAdmin]; |
| 1437 | |
| 1438 | // Apply filters |
| 1439 | if (status && status !== 'all') { |
no test coverage detected