Skip to content

Start-session page downloads every subject in the group, with personal information, to list custom IDs #1536

Description

@gdevenyi

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:

  1. 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.
  2. 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.
  3. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions