fix: add @IsNotEmpty, @ApiProperty, UUID pipe validation, and server-side identity derivation (closes #354 #355 #356 #357) - #469
Merged
chonilius merged 1 commit intoSep 28, 2026
Conversation
…y derivation (MergeFi#354 MergeFi#355 MergeFi#356 MergeFi#357) Issue MergeFi#354 — @isnotempty() missing from required free-text fields - CreateMilestoneDto.title: add @Transform(trim) + @isnotempty() - CreatePoolDto.name: add @Transform(trim) + @isnotempty() - CreateTeamDto.name: add @Transform(trim) + @isnotempty() + @maxlength(100) - MilestonesService.create: defensive .trim() on title at persistence - MaintenancePoolService.create: defensive .trim() on name at persistence - TeamsService.create: defensive .trim() on name at persistence - New unit specs: create-milestone.dto.spec.ts, create-pool.dto.spec.ts, create-team.dto.spec.ts covering blank/whitespace-only rejection Issue MergeFi#355 — inline controller DTOs missing @ApiProperty - DepositDto (amount, funderAddress) in maintenance-pool.controller.ts - AssignRewardDto (issueId, amount, recipientAddress, recipientId) same file - FundBountyDto (funderAddress) in bounties.controller.ts - SetStellarAddressDto (stellarAddress) in users.controller.ts - FundMilestoneDto (funderAddress) in milestones.controller.ts - ResolveIssueDto (issueId) same file Issue MergeFi#356 — repositoryId query param on GET /bounties not validated as UUID - Add ParseUUIDPipe({ version: '4', optional: true }) to repositoryId - New regression spec bounties.query.spec.ts: malformed UUID -> 400, valid UUID and omitted param pass through Issue MergeFi#357 — sponsorId/createdById null when MAINTAINER omits the field - MilestonesService.create: sponsorId = dto.sponsorId ?? callerUserId - MaintenancePoolService.create: createdById = dto.createdById ?? callerUserId - TeamsService.create: createdById = dto.createdById ?? callerUserId - MaintenancePoolController.create: extract userId from JWT req.user, pass through - TeamsController.create: use existing @currentuser() decorator, pass through - Update service specs to assert fallback-to-callerUserId behaviour
|
@Joeloo1 is attempting to deploy a commit to the chonilius' projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Joeloo1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR bundles fixes for four related validation and data-integrity issues reported across the DTO layer, Swagger documentation, query-parameter validation, and ownership attribution. All changes are backward-compatible at the HTTP surface; no migration is required.
Issue #354 — Required free-text fields accept blank/whitespace-only strings
Problem
A repo-wide search confirmed that no DTO used
@IsNotEmpty(). Every required string field was guarded by@IsString()alone, whichclass-validatorconsiders satisfied by""or" ". Three high-value fields were directly affected:CreateMilestoneDtotitle@IsString() @MaxLength(200)CreatePoolDtoname@IsString() @MaxLength(100)CreateTeamDtoname@IsString()(also missing@MaxLength)Fix
@Transform(({ value }) => typeof value === 'string' ? value.trim() : value)before the validators so leading/trailing whitespace is stripped at the pipe stage, matching the trim-then-validate pattern already in place for other fields.@IsNotEmpty()to all three fields.@MaxLength(100)toCreateTeamDto.name(tracked separately as CreateTeamDto.name has no @MaxLength, unlike every sibling free-text field the #151 fix covered #263 but trivially included here)..trim()in each service'screatemethod as a belt-and-suspenders guard at persistence time.Files changed:
src/milestones/dto/create-milestone.dto.tssrc/maintenance-pool/dto/create-pool.dto.tssrc/teams/dto/create-team.dto.tssrc/milestones/milestones.service.tssrc/maintenance-pool/maintenance-pool.service.tssrc/teams/teams.service.tsNew test files:
src/milestones/dto/create-milestone.dto.spec.ts— rejects empty/whitespace title, accepts trimmed titlesrc/maintenance-pool/dto/create-pool.dto.spec.ts— rejects empty/whitespace name, accepts trimmed namesrc/teams/dto/create-team.dto.spec.ts— rejects empty/whitespace name, accepts trimmed nameIssue #355 — Six inline controller DTOs have no
@ApiPropertyannotationsProblem
grep -n "ApiProperty" src/maintenance-pool/maintenance-pool.controller.ts src/bounties/bounties.controller.ts src/users/users.controller.ts src/milestones/milestones.controller.tsreturned zero matches. Every DTO defined inline inside a controller file was invisible to Swagger/OpenAPI tooling, making the generated API spec incomplete and untestable via Swagger UI.The affected DTOs were:
DepositDtomaintenance-pool.controller.tsamount,funderAddressAssignRewardDtomaintenance-pool.controller.tsissueId,amount,recipientAddress,recipientIdFundBountyDtobounties.controller.tsfunderAddressSetStellarAddressDtousers.controller.tsstellarAddressFundMilestoneDtomilestones.controller.tsfunderAddressResolveIssueDtomilestones.controller.tsissueIdFix
Added
@ApiProperty({ description: '...' })to every field in each of the six DTOs, using the same annotation style already established by the dedicateddto/files (e.g.FundEscrowDto,CreateBountyDto). Optional fields includerequired: false.Files changed:
src/maintenance-pool/maintenance-pool.controller.tssrc/bounties/bounties.controller.tssrc/users/users.controller.tssrc/milestones/milestones.controller.tsIssue #356 —
repositoryIdquery param onGET /bountiesnot validated as UUIDProblem
GET /bountiesaccepts five query filters. Three (status,difficulty,asset) are pipe-validated.repositoryId, despite being a UUID foreign key used in aWHEREclause, was bound as a rawstring?with no pipe. A malformed value (e.g.repositoryId=not-a-uuid) bypassed the application layer and reached the PostgreSQL driver, which threwinvalid input syntax for type uuid— an unhandled 500 on a public, unauthenticated endpoint throttled at 1 000 req/hour.TypeORM's parameterization already prevents SQL injection; this fix converts the driver-level crash into the clean
400 Bad Requestthat every other filter on the same route returns.Files changed:
src/bounties/bounties.controller.tsNew test file:
src/bounties/bounties.query.spec.ts— asserts that a non-UUIDrepositoryIdyields a 400, a valid UUID and an absent param both pass through cleanly.Issue #357 —
sponsorId/createdByIdpersisted asnullwhen a MAINTAINER omits the fieldProblem
CreateBountyDto.sponsorIdis@IsUUID()(required). The structurally identical ownership fields on the other three create DTOs are@IsOptional() @IsUUID(), meaning a MAINTAINER — who is a valid caller of all@Roles(SPONSOR, MAINTAINER)-gated create routes — can omit the field entirely, producing rows wheresponsorId = null/createdById = null.This silently breaks downstream queries in
SponsorsService:activeMilestones: filtersWHERE milestone.sponsorId = :userId— null rows never appeardashboard/milestoneProgress: same filter — the milestone is permanently invisible to any sponsor dashboardThe same null propagation affects
MaintenancePoolService(pool attribution) andTeamsService(team creator attribution).Fix — server-side identity derivation
Rather than making the DTO field required (which would break MAINTAINER callers who have no sponsor ID to supply), the server now derives the value from the authenticated JWT when the client omits it:
Each service's
createmethod now accepts acallerUserId: stringparameter. The controllers extract it from the authenticated request:MilestonesController.create— already passedcallerUserId(no change needed)MaintenancePoolController.create—@Req() req: Requestadded;req.user.userIdextracted and forwardedTeamsController.create— uses the existing@CurrentUser()decorator (already imported for theassignendpoint)The DTO fields remain
@IsOptional()— the server handles the missing value gracefully rather than forcing clients to re-send their own user ID (which they shouldn't be trusted to self-report anyway).Files changed:
src/milestones/milestones.service.tssrc/maintenance-pool/maintenance-pool.service.tssrc/teams/teams.service.tssrc/maintenance-pool/maintenance-pool.controller.tssrc/teams/teams.controller.tsUpdated test files:
src/maintenance-pool/maintenance-pool.service.spec.ts— updatedcreatetests to passcallerUserId; the "defaults to null" test is replaced with "falls back to callerUserId"src/teams/teams.service.spec.ts— same updatesTest coverage summary
create-milestone.dto.spec.ts(new)create-pool.dto.spec.ts(new)create-team.dto.spec.ts(new)bounties.query.spec.ts(new)repositoryId→ 400; valid UUID and absent param passmaintenance-pool.service.spec.ts(updated)callerUserIdused as fallback forcreatedByIdteams.service.spec.ts(updated)callerUserIdused as fallback forcreatedByIdbounties.controller.spec.ts(updated)ParseUUIDPipeonrepositoryIdCloses #354
Closes #355
Closes #356
Closes #357