Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export class MeetingRegistrantsComponent implements OnInit {
if (!uid) return;

this.meetingService
.getMeetingRegistrants(uid)
.getMeetingRegistrants(uid, false)
.pipe(
take(1),
catchError((error) => {
Expand Down
35 changes: 33 additions & 2 deletions apps/lfx-one/src/app/shared/services/meeting.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
BatchRegistrantOperationResponse,
CreateMeetingRegistrantRequest,
CreateMeetingRequest,
CreateMeetingRsvpRequest,
GenerateAgendaRequest,
GenerateAgendaResponse,
Meeting,
MeetingAttachment,
MeetingJoinURL,
MeetingRegistrant,
MeetingRegistrantWithState,
MeetingRsvp,
PastMeeting,
PastMeetingParticipant,
PastMeetingRecording,
Expand Down Expand Up @@ -304,8 +306,9 @@ export class MeetingService {
);
}

public getMeetingRegistrants(meetingUid: string): Observable<MeetingRegistrant[]> {
return this.http.get<MeetingRegistrant[]>(`/api/meetings/${meetingUid}/registrants`).pipe(
public getMeetingRegistrants(meetingUid: string, includeRsvp: boolean = false): Observable<MeetingRegistrant[]> {
const params = new HttpParams().set('include_rsvp', includeRsvp.toString());
return this.http.get<MeetingRegistrant[]>(`/api/meetings/${meetingUid}/registrants`, { params }).pipe(
catchError((error) => {
console.error(`Failed to load registrants for meeting ${meetingUid}:`, error);
return of([]);
Expand Down Expand Up @@ -438,6 +441,34 @@ export class MeetingService {
);
}

public createMeetingRsvp(meetingUid: string, request: CreateMeetingRsvpRequest): Observable<MeetingRsvp> {
return this.http.post<MeetingRsvp>(`/api/meetings/${meetingUid}/rsvp`, request).pipe(
take(1),
catchError((error) => {
console.error(`Failed to create RSVP for meeting ${meetingUid}:`, error);
return throwError(() => error);
})
);
}

public getUserMeetingRsvp(meetingUid: string): Observable<MeetingRsvp | null> {
return this.http.get<MeetingRsvp | null>(`/api/meetings/${meetingUid}/rsvp`).pipe(
catchError((error) => {
console.error(`Failed to get RSVP for meeting ${meetingUid}:`, error);
return of(null);
})
);
}

public getMeetingRsvps(meetingUid: string): Observable<MeetingRsvp[]> {
return this.http.get<MeetingRsvp[]>(`/api/meetings/${meetingUid}/rsvps`).pipe(
catchError((error) => {
console.error(`Failed to get RSVPs for meeting ${meetingUid}:`, error);
return of([]);
})
);
}

private readFileAsBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
Expand Down
143 changes: 139 additions & 4 deletions apps/lfx-one/src/server/controllers/meeting.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
BatchRegistrantOperationResponse,
CreateMeetingRegistrantRequest,
CreateMeetingRequest,
CreateMeetingRsvpRequest,
UpdateMeetingRegistrantRequest,
UpdateMeetingRequest,
} from '@lfx-one/shared/interfaces';
Expand Down Expand Up @@ -289,8 +290,12 @@ export class MeetingController {
*/
public async getMeetingRegistrants(req: Request, res: Response, next: NextFunction): Promise<void> {
const { uid } = req.params;
const { include_rsvp } = req.query;
const includeRsvp = include_rsvp === 'true';

const startTime = Logger.start(req, 'get_meeting_registrants', {
meeting_uid: uid,
include_rsvp: includeRsvp,
});

try {
Expand All @@ -306,11 +311,12 @@ export class MeetingController {
}

// Get the meeting registrants
const registrants = await this.meetingService.getMeetingRegistrants(req, uid);
const registrants = await this.meetingService.getMeetingRegistrants(req, uid, includeRsvp);

Logger.success(req, 'get_meeting_registrants', startTime, {
meeting_uid: uid,
registrant_count: registrants.length,
include_rsvp: includeRsvp,
});

// Send the registrants data to the client
Expand All @@ -319,6 +325,7 @@ export class MeetingController {
// Log the error
Logger.error(req, 'get_meeting_registrants', startTime, error, {
meeting_uid: uid,
include_rsvp: includeRsvp,
});

// Send the error to the next middleware
Expand Down Expand Up @@ -723,6 +730,137 @@ export class MeetingController {
}
}

/**
* POST /meetings/:uid/rsvp
*/
public async createMeetingRsvp(req: Request, res: Response, next: NextFunction): Promise<void> {
const { uid } = req.params;
const rsvpData: CreateMeetingRsvpRequest = req.body;

const startTime = Logger.start(req, 'create_meeting_rsvp', {
meeting_uid: uid,
registrant_id: rsvpData.registrant_id,
response: rsvpData.response,
scope: rsvpData.scope,
});

try {
// Validate meeting UID
if (
!validateUidParameter(uid, req, next, {
operation: 'create_meeting_rsvp',
})
) {
return;
}

// Validate RSVP data
if (!rsvpData.response || !rsvpData.scope) {
throw ServiceValidationError.fromFieldErrors(
{
response: !rsvpData.response ? 'Response is required' : [],
scope: !rsvpData.scope ? 'Scope is required' : [],
},
'RSVP data validation failed',
{
operation: 'create_meeting_rsvp',
service: 'meeting_controller',
}
);
}

// Create the RSVP
const rsvp = await this.meetingService.createMeetingRsvp(req, uid, rsvpData);

// Log success
Logger.success(req, 'create_meeting_rsvp', startTime, {
rsvp_id: rsvp.id,
});

// Send response
res.json(rsvp);
} catch (error) {
// Log error
Logger.error(req, 'create_meeting_rsvp', startTime, error);
next(error);
}
}

/**
* GET /meetings/:uid/rsvp
*/
public async getUserMeetingRsvp(req: Request, res: Response, next: NextFunction): Promise<void> {
const { uid } = req.params;

const startTime = Logger.start(req, 'get_user_meeting_rsvp', {
meeting_uid: uid,
});

try {
// Validate meeting UID
if (
!validateUidParameter(uid, req, next, {
operation: 'get_user_meeting_rsvp',
})
) {
return;
}

// Get the user's RSVP
const rsvp = await this.meetingService.getUserMeetingRsvp(req, uid);

// Log success
Logger.success(req, 'get_user_meeting_rsvp', startTime, {
found: !!rsvp,
rsvp_id: rsvp?.id,
});

// Send response
res.json(rsvp);
} catch (error) {
// Log error
Logger.error(req, 'get_user_meeting_rsvp', startTime, error);
next(error);
}
}

/**
* GET /meetings/:uid/rsvps
*/
public async getMeetingRsvps(req: Request, res: Response, next: NextFunction): Promise<void> {
const { uid } = req.params;

const startTime = Logger.start(req, 'get_meeting_rsvps', {
meeting_uid: uid,
});

try {
// Validate meeting UID
if (
!validateUidParameter(uid, req, next, {
operation: 'get_meeting_rsvps',
})
) {
return;
}

// Get all RSVPs for the meeting
const rsvps = await this.meetingService.getMeetingRsvps(req, uid);

// Log success
Logger.success(req, 'get_meeting_rsvps', startTime, {
count: rsvps.length,
});

// Send response
res.json(rsvps);
} catch (error) {
// Log error
Logger.error(req, 'get_meeting_rsvps', startTime, error);
next(error);
}
}

/**
* Private helper to process registrant operations with fail-fast for 403 errors
*/
Expand Down Expand Up @@ -779,9 +917,6 @@ export class MeetingController {
}
}

/**
* Private helper to create batch response from results
*/
private createBatchResponse<T, I>(
results: PromiseSettledResult<T>[],
inputData: I[],
Expand Down
5 changes: 5 additions & 0 deletions apps/lfx-one/src/server/routes/meetings.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ router.delete('/:uid/registrants', (req, res, next) => meetingController.deleteM
// POST /meetings/:uid/registrants/:registrantId/resend - resend invitation to specific registrant
router.post('/:uid/registrants/:registrantId/resend', (req, res, next) => meetingController.resendMeetingInvitation(req, res, next));

// RSVP routes
router.post('/:uid/rsvp', (req, res, next) => meetingController.createMeetingRsvp(req, res, next));
router.get('/:uid/rsvp', (req, res, next) => meetingController.getUserMeetingRsvp(req, res, next));
router.get('/:uid/rsvps', (req, res, next) => meetingController.getMeetingRsvps(req, res, next));

router.post('/:uid/attachments/upload', async (req: Request, res: Response, next: NextFunction) => {
const startTime = Date.now();
const meetingId = req.params['uid'];
Expand Down
Loading