mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
fix(assignments): confine day participants to the trip roster
setParticipants took the ids on trust. The caller is proven to be on the trip and the assignment is proven to sit on it, but nothing said the same about the ids in the body, and the read-back joins users — so a stranger's name and avatar came back to the writer and went out over the assignment:participants broadcast to everyone else in the room. Takes the tripId both callers already hold and filters against the roster. Same drop-rather-than-reject rule as bag members and reservation travellers: the participants box posts the whole list on every edit, so a trip whose membership changed underneath it would otherwise be stuck. ASG-SVC-028 covered a foreign key error rolling the replace back. The filter takes an unknown id out before the insert can reach it, so that path is gone and the case now pins what happens instead: a list of nothing but strangers clears, like the empty list ASG-SVC-018 already pins.
This commit is contained in:
@@ -202,7 +202,7 @@ export class AssignmentOpsController {
|
||||
if (!this.assignments.getAssignmentForTrip(id, tripId)) {
|
||||
throw new HttpException({ error: 'Assignment not found' }, 404);
|
||||
}
|
||||
const participants = this.assignments.setParticipants(id, body.user_ids);
|
||||
const participants = this.assignments.setParticipants(id, body.user_ids, tripId);
|
||||
this.assignments.broadcast(tripId, 'assignment:participants', { assignmentId: Number(id), participants }, socketId);
|
||||
return { participants };
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ export class AssignmentsMcp {
|
||||
if (!this.assignments.verifyTripAccess(tripId, ctx.userId)) return noAccess();
|
||||
if (!this.guards.hasTripPermission('day_edit', tripId, ctx.userId)) return permissionDenied();
|
||||
if (!this.assignments.getAssignmentForTrip(assignmentId, tripId)) return errorResult('Assignment not found.');
|
||||
const participants = this.assignments.setParticipants(assignmentId, userIds);
|
||||
const participants = this.assignments.setParticipants(assignmentId, userIds, tripId);
|
||||
this.guards.safeBroadcast(tripId, 'assignment:participants', { assignmentId, participants });
|
||||
return ok({ participants });
|
||||
}
|
||||
|
||||
@@ -283,12 +283,21 @@ export class AssignmentsService {
|
||||
return this.getAssignmentWithPlace(Number(id));
|
||||
}
|
||||
|
||||
setParticipants(assignmentId: string | number, userIds: number[]) {
|
||||
/**
|
||||
* Both callers have already proven the assignment sits on `tripId`; this
|
||||
* settles the other half, that the ids in the body do too. Off-roster ids drop
|
||||
* silently rather than 400, matching bag members and reservation travellers —
|
||||
* the participants box sends the whole list back on every edit, so rejecting
|
||||
* the request would strand a trip whose membership changed underneath it.
|
||||
*/
|
||||
setParticipants(assignmentId: string | number, userIds: number[], tripId: string | number) {
|
||||
const roster = this.dbs.rosterUserIds(tripId);
|
||||
const scoped = userIds.filter(id => roster.has(id));
|
||||
this.dbs.transaction(() => {
|
||||
this.dbs.run('DELETE FROM assignment_participants WHERE assignment_id = ?', assignmentId);
|
||||
if (userIds.length > 0) {
|
||||
if (scoped.length > 0) {
|
||||
const insert = this.dbs.prepare('INSERT OR IGNORE INTO assignment_participants (assignment_id, user_id) VALUES (?, ?)');
|
||||
for (const userId of userIds) insert.run(assignmentId, userId);
|
||||
for (const userId of scoped) insert.run(assignmentId, userId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user