| 929 | } |
| 930 | |
| 931 | private async getUpcomingEvents(userId: string, userEmail?: string): Promise<{ id: string; title: string; start_time: string; co_attendee_count: number }[]> { |
| 932 | // Match registrations by workos_user_id OR email (Luma-synced registrations only have email) |
| 933 | const userMatch = userEmail |
| 934 | ? `(er.workos_user_id = $1 OR LOWER(er.email) = LOWER($2))` |
| 935 | : `er.workos_user_id = $1`; |
| 936 | const excludeSelf = userEmail |
| 937 | ? `AND er2.workos_user_id != $1 AND LOWER(er2.email) != LOWER($2)` |
| 938 | : `AND er2.workos_user_id != $1`; |
| 939 | const params = userEmail ? [userId, userEmail] : [userId]; |
| 940 | |
| 941 | const result = await query<{ id: string; title: string; start_time: string; co_attendee_count: number }>( |
| 942 | `SELECT DISTINCT e.id, e.title, e.start_time, |
| 943 | (SELECT COUNT(*) FROM event_registrations er2 |
| 944 | WHERE er2.event_id = e.id ${excludeSelf} |
| 945 | AND er2.registration_status IN ('registered', 'waitlisted')) as co_attendee_count |
| 946 | FROM events e |
| 947 | JOIN event_registrations er ON er.event_id = e.id |
| 948 | WHERE ${userMatch} |
| 949 | AND er.registration_status IN ('registered', 'waitlisted') |
| 950 | AND e.start_time > NOW() |
| 951 | AND e.status = 'published' |
| 952 | ORDER BY e.start_time ASC |
| 953 | LIMIT 5`, |
| 954 | params |
| 955 | ); |
| 956 | return result.rows; |
| 957 | } |
| 958 | |
| 959 | private async getUserWorkingGroupsWithCount(userId: string): Promise<{ id: string; name: string; slug: string; member_count: number }[]> { |
| 960 | const result = await query<{ id: string; name: string; slug: string; member_count: number }>( |