Current Implementation and Limitations
The start-session component (start-session.tsx#L33) and its route loader (#L124) both use subjectsQueryOptions({ params: { groupId } }). They share one query key, so the page makes one GET /v1/subjects?groupId= request. SubjectsService.find returns every subject in the group, with no pagination and no field selection (subjects.service.ts#L144). Each subject includes name, date of birth, sex and groupIds.
The page uses this list only to build customSubjectIds (start-session.tsx#L35-L37). These are the IDs of the subjects for which isSubjectWithPersonalInfo is false, that is, subjects with at least one of firstName, lastName, dateOfBirth or sex missing (subject-utils index.ts#L42-L50). The page never shows the personal information.
This causes three problems:
- Size. Each subject is about 313 bytes with personal information and about 236 bytes without. A group with 15,000 subjects returns about 4.5 MB on each load. API responses are not compressed: the
/api/* block in the Caddyfile has no encode (Caddyfile#L4-L6). useSuspenseQuery sets a minimum staleTime of 1 second, and refetchOnWindowFocus is on. Thus the list refetches on each visit, and each time the tab becomes visible again.
- Data minimization. Each visit sends the names and dates of birth of all subjects in the group to the browser. A subject with a custom ID can also have a date of birth and sex, and the response sends them too. The page needs none of this information.
- No group.
groupId is optional (subjects.controller.ts#L38-L46). An admin with no current group sends no groupId, and gets every subject in the instance.
Related, but not in the scope of this issue: the datahub loads the same full list with the same query key (datahub/index.tsx#L481, #L509), and a second list with hasRecord (#L343-L345). A fix for the start-session page does not change the datahub.
Associated Application Components
Client, Server
Proposed Solution
Add an endpoint that returns only the IDs that the page needs. Filter in the MongoDB query, not in JavaScript. The response is then a list of short strings with no personal information.
- Path.
SubjectsController has @Get(':id') (subjects.controller.ts#L49), so apps/api/AGENTS.md asks for a path with more than one segment. For example, use GET /v1/subjects/groups/:groupId/custom-ids. A required groupId also removes problem 3: an admin with no current group gets no suggestions.
- Filter. For each of the four fields, match
null or { isSet: false }. On MongoDB, a Prisma filter for null does not match a document where the field is missing. InstrumentsService.find uses the same pattern for seriesGroupId (instruments.service.ts#L291-L295).
- Scope. Put
accessibleQuery(ability, 'read', 'Subject') and { groupIds: { has: groupId } } in the where, and have the controller forward @CurrentUser('ability'). Validate groupId with ValidObjectIdPipe.
- Response. Use
select: { id: true }.
If the custom-ID list itself can grow large, change the field to a type-ahead search with a result limit.
Related: PR #1426 moves a similar subject-ID computation into the database.
Tests. Unit tests for the new service method: it excludes a subject with full personal information, it includes a subject whose field is null and a subject whose field is missing, and it excludes subjects of other groups. An e2e test for the same exclusion and group scoping. The e2e test that the start-session page suggests custom IDs already exists (testing/src/specs/start-session.spec.ts), and it must continue to pass.
Estimated Difficulty
Medium
Priority
Medium
Current Implementation and Limitations
The start-session component (start-session.tsx#L33) and its route loader (#L124) both use
subjectsQueryOptions({ params: { groupId } }). They share one query key, so the page makes oneGET /v1/subjects?groupId=request.SubjectsService.findreturns every subject in the group, with no pagination and no field selection (subjects.service.ts#L144). Each subject includes name, date of birth, sex andgroupIds.The page uses this list only to build
customSubjectIds(start-session.tsx#L35-L37). These are the IDs of the subjects for whichisSubjectWithPersonalInfois false, that is, subjects with at least one offirstName,lastName,dateOfBirthorsexmissing (subject-utils index.ts#L42-L50). The page never shows the personal information.This causes three problems:
/api/*block in theCaddyfilehas noencode(Caddyfile#L4-L6).useSuspenseQuerysets a minimumstaleTimeof 1 second, andrefetchOnWindowFocusis on. Thus the list refetches on each visit, and each time the tab becomes visible again.groupIdis optional (subjects.controller.ts#L38-L46). An admin with no current group sends nogroupId, and gets every subject in the instance.Related, but not in the scope of this issue: the datahub loads the same full list with the same query key (datahub/index.tsx#L481, #L509), and a second list with
hasRecord(#L343-L345). A fix for the start-session page does not change the datahub.Associated Application Components
Client, Server
Proposed Solution
Add an endpoint that returns only the IDs that the page needs. Filter in the MongoDB query, not in JavaScript. The response is then a list of short strings with no personal information.
SubjectsControllerhas@Get(':id')(subjects.controller.ts#L49), soapps/api/AGENTS.mdasks for a path with more than one segment. For example, useGET /v1/subjects/groups/:groupId/custom-ids. A requiredgroupIdalso removes problem 3: an admin with no current group gets no suggestions.nullor{ isSet: false }. On MongoDB, a Prisma filter fornulldoes not match a document where the field is missing.InstrumentsService.finduses the same pattern forseriesGroupId(instruments.service.ts#L291-L295).accessibleQuery(ability, 'read', 'Subject')and{ groupIds: { has: groupId } }in thewhere, and have the controller forward@CurrentUser('ability'). ValidategroupIdwithValidObjectIdPipe.select: { id: true }.If the custom-ID list itself can grow large, change the field to a type-ahead search with a result limit.
Related: PR #1426 moves a similar subject-ID computation into the database.
Tests. Unit tests for the new service method: it excludes a subject with full personal information, it includes a subject whose field is null and a subject whose field is missing, and it excludes subjects of other groups. An e2e test for the same exclusion and group scoping. The e2e test that the start-session page suggests custom IDs already exists (
testing/src/specs/start-session.spec.ts), and it must continue to pass.Estimated Difficulty
Medium
Priority
Medium