Add consistent field-level filtering to list endpoints via a shared query DTO base
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 25/100
- Issue type
- Refactor
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- nodejs, typescript
Research direction
Start with the listFiles handler in src/infrastructure/file-upload/file-upload.controller.ts (around lines 116-121) and the QueryAuditLogDto used in src/infrastructure/audit/audit-log.service.ts, then read the existing cursor utility in src/common/pagination/. Done means the shared pagination and sort DTO bases exist, the file-upload query is migrated with its limit?: number annotation fixed, and tests cover default, boundary and over-limit values. Parameter renames are breaking, so each endpoint needs its own PR and a CHANGELOG.md entry.
Written by the indexing model from the issue text.
Description
Problem
List endpoints take ad-hoc query parameters. src/infrastructure/file-upload/file-upload.controller.ts:116-121 is representative:
async listFiles(
@Query("name") name?: string,
@Query("category") category?: string,
@Query("limit") limit?: number,
@Query("offset") offset?: number,
) {
Note the type annotation limit?: number on a @Query() value — with transform: true the value arrives as a string, so this annotation is wrong and the ceiling is limit || 20 with no maximum.
Meanwhile src/infrastructure/audit/audit-log.service.ts uses a proper QueryAuditLogDto. The two approaches coexist, and there are 61 DTO files with no shared base.
Why it matters
Without a shared base, each list endpoint invents its own parameter names and semantics: page+limit in one place, limit+offset in another, unbounded in a third. Clients cannot predict the API, and each new endpoint repeats the same mistakes — unbounded limits, unvalidated strings, no sort control.
The @Query("limit") limit?: number annotation is also actively misleading: it tells a reader the value is a number when it is a string, which is precisely the kind of thing that produces a NaN reaching a database query.
Suggested Direction
- Create shared query DTOs in
src/common/pagination/(or a newsrc/common/dto/) alongside the existing cursor utility:
Noteexport class PaginationQueryDto { @IsOptional() @Type(() => Number) @IsInt() @Min(1) @Max(100) limit?: number = 20; @IsOptional() @IsString() cursor?: string; } export class SortQueryDto { @IsOptional() @IsIn(['createdAt', 'updatedAt', 'name']) sortBy?: string; @IsOptional() @IsIn(['ASC', 'DESC']) sortDirection?: 'ASC' | 'DESC' = 'DESC'; }@Type(() => Number)fromclass-transformer— required fortransform: trueto coerce query strings. - Have every list DTO extend them, so
limit,sortBy,sortDirectionand cursor semantics are identical everywhere. - Migrate incrementally, one controller at a time, starting with
file-upload.controller.tsand the audit query. Fixing the wronglimit?: numberannotation is a natural first step. - Reject unknown query parameters. The global pipe already sets
forbidNonWhitelisted: true, but that only applies to DTO-typed bodies/queries — a@Query("name")string parameter bypasses validation entirely. Moving to DTOs closes that gap.
Constraints
- Changing parameter names is a breaking API change for affected endpoints. Migrate one endpoint per PR, and note each in
CHANGELOG.md. - Keep the structured 400 validation-error shape intact — clients depend on it.
- Ensure
@Max()ceilings are set; see the pagination-bounds issue, which is complementary. forbidUnknownValues: trueis already set on the global pipe; verify it behaves correctly with these DTOs.
Acceptance Criteria
- Shared pagination and sort DTO bases exist and are documented.
- Every list DTO extends the base, or a documented migration list names those that do not yet.
- No
@Query("...") x?: numberpatterns remain (values are strings until transformed). - Every list endpoint enforces a maximum
limit. - Unknown query parameters on migrated endpoints return
400. - Tests cover default, boundary, and over-limit values.
- Dominant language
- TypeScript
- Stars
- 7
- Forks
- 55
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 5
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from SourceXXL/alian_structure-api
-
good first issue help wanted security
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SourceXXL/alian_structure-api#266 ·
-
Parallelize the cache-warming loop in `cache-warming.service.ts` — warmers run strictly sequentiallyPossibly taken @syntaxsuuvam-spec claimed this 4 days ago. Opengood first issue performance
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SourceXXL/alian_structure-api#240 ·
-
documentation help wanted intermediate refactor
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
SourceXXL/alian_structure-api#215 ·
-
good first issue refactor tooling
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
SourceXXL/alian_structure-api#199 ·
-
help wanted intermediate refactor
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
SourceXXL/alian_structure-api#196 ·
All issues in SourceXXL/alian_structure-api
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
xdjs/MusicNerdWeb#1532 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
sebastian-software/ardo#352 ·
Maintainers usually reply within 1 day
-
@react95/core good first issue
Difficulty 1/5 Under an hour Newbie friendliness 88/100
Maintainers usually reply within 3 days
-
@shikijs/stream: `ShikiStreamTokenizer.clone()` shares its `tokensStable` array with the originalPossibly taken @xyrolle claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 6 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day