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
22 changes: 20 additions & 2 deletions apps/lfx-pcc/src/app/shared/services/meeting.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ export class MeetingService {
);
}

public getPastMeetings(params?: HttpParams): Observable<Meeting[]> {
return this.http.get<Meeting[]>('/api/past-meetings', { params }).pipe(
catchError((error) => {
console.error('Failed to load past meetings:', error);
return of([]);
})
);
}

public getMeetingsByProject(projectId: string, limit?: number, orderBy?: string): Observable<Meeting[]> {
let params = new HttpParams().set('tags', `project_uid:${projectId}`);

Expand Down Expand Up @@ -70,12 +79,11 @@ export class MeetingService {
public getPastMeetingsByProject(projectId: string, limit: number = 3): Observable<Meeting[]> {
let params = new HttpParams().set('tags', `project_uid:${projectId}`);

// TODO: Add filter for past meetings
if (limit) {
params = params.set('limit', limit.toString());
}

return this.getMeetings(params);
return this.getPastMeetings(params);
}

public getMeeting(id: string): Observable<Meeting> {
Expand All @@ -88,6 +96,16 @@ export class MeetingService {
);
}

public getPastMeeting(id: string): Observable<Meeting> {
return this.http.get<Meeting>(`/api/past-meetings/${id}`).pipe(
catchError((error) => {
console.error(`Failed to load past meeting ${id}:`, error);
return throwError(() => error);
}),
tap((meeting) => this.meeting.set(meeting))
);
}

public getPublicMeeting(id: string): Observable<{ meeting: Meeting; project: Project }> {
return this.http.get<{ meeting: Meeting; project: Project }>(`/public/api/meetings/${id}`).pipe(
catchError((error) => {
Expand Down
128 changes: 128 additions & 0 deletions apps/lfx-pcc/src/server/controllers/past-meeting.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

import { NextFunction, Request, Response } from 'express';

import { Logger } from '../helpers/logger';
import { validateUidParameter } from '../helpers/validation.helper';
import { MeetingService } from '../services/meeting.service';

/**
* Controller for handling past meeting HTTP requests
*/
export class PastMeetingController {
private meetingService: MeetingService = new MeetingService();

/**
* GET /past-meetings
*/
public async getPastMeetings(req: Request, res: Response, next: NextFunction): Promise<void> {
const startTime = Logger.start(req, 'get_past_meetings', {
query_params: Logger.sanitize(req.query as Record<string, any>),
});

try {
// Get the past meetings using meetingType 'past_meeting'
const meetings = await this.meetingService.getMeetings(req, req.query as Record<string, any>, 'past_meeting');

// TODO: Remove this once we have a way to get the registrants count
// Process each meeting individually to add registrant counts
await Promise.all(
meetings.map(async (meeting) => {
const counts = await this.addRegistrantCounts(req, meeting.uid);
meeting.individual_registrants_count = counts.individual_registrants_count;
meeting.committee_members_count = counts.committee_members_count;
})
);

// Log the success
Logger.success(req, 'get_past_meetings', startTime, {
meeting_count: meetings.length,
});

// Send the meetings data to the client
res.json(meetings);
} catch (error) {
// Log the error
Logger.error(req, 'get_past_meetings', startTime, error);
next(error);
}
}

/**
* GET /past-meetings/:uid
*/
public async getPastMeetingById(req: Request, res: Response, next: NextFunction): Promise<void> {
const { uid } = req.params;
const startTime = Logger.start(req, 'get_past_meeting_by_id', {
meeting_uid: uid,
});

try {
// Check if the meeting UID is provided
if (
!validateUidParameter(uid, req, next, {
operation: 'get_past_meeting_by_id',
service: 'past_meeting_controller',
logStartTime: startTime,
})
) {
return;
}

// Get the past meeting by ID using meetingType 'past_meeting'
const meeting = await this.meetingService.getMeetingById(req, uid, 'past_meeting');

// Log the success
Logger.success(req, 'get_past_meeting_by_id', startTime, {
meeting_uid: uid,
project_uid: meeting.project_uid,
title: meeting.title,
});

// TODO: Remove this once we have a way to get the registrants count
const counts = await this.addRegistrantCounts(req, meeting.uid);
meeting.individual_registrants_count = counts.individual_registrants_count;
meeting.committee_members_count = counts.committee_members_count;

// Send the meeting data to the client
res.json(meeting);
} catch (error) {
// Log the error
Logger.error(req, 'get_past_meeting_by_id', startTime, error, {
meeting_uid: uid,
});

// Send the error to the next middleware
next(error);
}
}

/**
* Helper method to add registrant counts to a meeting
* @param req - Express request object
* @param meetingUid - UID of the meeting
* @returns Promise with registrant counts or defaults to 0 on error
*/
private async addRegistrantCounts(req: Request, meetingUid: string): Promise<{ individual_registrants_count: number; committee_members_count: number }> {
try {
const registrants = await this.meetingService.getMeetingRegistrants(req, meetingUid);
const committeeMembers = registrants.filter((r) => r.type === 'committee').length ?? 0;

return {
individual_registrants_count: registrants.length - committeeMembers,
committee_members_count: committeeMembers,
};
} catch (error) {
// Log error but don't fail - default to 0 counts
Logger.error(req, 'add_registrant_counts', Date.now(), error, {
meeting_uid: meetingUid,
});

return {
individual_registrants_count: 0,
committee_members_count: 0,
};
}
}
}
17 changes: 17 additions & 0 deletions apps/lfx-pcc/src/server/routes/past-meetings.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright The Linux Foundation and each contributor to LFX.
// SPDX-License-Identifier: MIT

import express from 'express';

import { PastMeetingController } from '../controllers/past-meeting.controller';

const router = express.Router();
const pastMeetingController = new PastMeetingController();

// Past meeting routes
router.get('/', (req, res, next) => pastMeetingController.getPastMeetings(req, res, next));

// Get past meeting by UID
router.get('/:uid', (req, res, next) => pastMeetingController.getPastMeetingById(req, res, next));

export default router;
2 changes: 2 additions & 0 deletions apps/lfx-pcc/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { apiErrorHandler } from './middleware/error-handler.middleware';
import { protectedRoutesMiddleware } from './middleware/protected-routes.middleware';
import committeesRouter from './routes/committees.route';
import meetingsRouter from './routes/meetings.route';
import pastMeetingsRouter from './routes/past-meetings.route';
import permissionsRouter from './routes/permissions.route';
import projectsRouter from './routes/projects.route';
import publicMeetingsRouter from './routes/public-meetings.route';
Expand Down Expand Up @@ -202,6 +203,7 @@ app.use('/api/projects', projectsRouter);
app.use('/api/projects', permissionsRouter);
app.use('/api/committees', committeesRouter);
app.use('/api/meetings', meetingsRouter);
app.use('/api/past-meetings', pastMeetingsRouter);

// Add API error handler middleware
app.use('/api/*', apiErrorHandler);
Expand Down
8 changes: 4 additions & 4 deletions apps/lfx-pcc/src/server/services/meeting.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export class MeetingService {
/**
* Fetches all meetings based on query parameters
*/
public async getMeetings(req: Request, query: Record<string, any> = {}): Promise<Meeting[]> {
public async getMeetings(req: Request, query: Record<string, any> = {}, meetingType: string = 'meeting'): Promise<Meeting[]> {
const params = {
...query,
type: 'meeting',
type: meetingType,
};

const { resources } = await this.microserviceProxy.proxyRequest<QueryServiceResponse<Meeting>>(req, 'LFX_V2_SERVICE', '/query/resources', 'GET', params);
Expand All @@ -61,9 +61,9 @@ export class MeetingService {
/**
* Fetches a single meeting by UID
*/
public async getMeetingById(req: Request, meetingUid: string): Promise<Meeting> {
public async getMeetingById(req: Request, meetingUid: string, meetingType: string = 'meeting'): Promise<Meeting> {
const params = {
type: 'meeting',
type: meetingType,
tags: `meeting_uid:${meetingUid}`,
};

Expand Down