Implement schema-first OpenAPI definition and validation #3

Merged
jonasclaes merged 21 commits from feat/implement-openapi-schema-first into main 2026-03-15 15:48:47 +00:00
jonasclaes commented 2026-03-15 15:32:36 +00:00 (Migrated from github.com)
No description provided.
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026-03-15 15:38:49 +00:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull request overview

This PR shifts the API to a schema-first approach by introducing an OpenAPI 3 spec, generating Go models from it, and validating incoming requests via OpenAPI middleware. It also replaces the previous swagger annotation-based approach and introduces a built-in Swagger UI for interactive documentation.

Changes:

  • Add openapi-spec.yaml + oapi-codegen output (pkg/api) and wire request validation middleware into the server.
  • Refactor handlers to decode/encode via generated api.* types and use explicit mapping helpers.
  • Replace prior auth middleware with an OpenAPI AuthenticationFunc implementation and serve Swagger UI + spec endpoints.

Reviewed changes

Copilot reviewed 29 out of 39 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
pkg/version/models.go Removes request tags/examples previously used for validator/swagger generation.
pkg/version/handler.go Routes moved under /v1/*; uses generated request/response models + mapping helpers.
pkg/user/models.go Removes request tags/examples and old response conversion helpers.
pkg/user/handler.go Drops router-level auth middleware usage; maps to/from generated models.
pkg/project/models.go Removes request tags/examples and old response conversion helpers.
pkg/project/handler.go Routes moved under /v1/*; uses generated request/response models + mapping helpers.
pkg/platform/swagger/swagger.go Adds endpoints to serve OpenAPI spec (JSON/YAML) and embedded Swagger UI.
pkg/platform/swagger/swagger-ui/swagger-initializer.js Swagger UI bootstrap pointing to /swagger/doc.json.
pkg/platform/swagger/swagger-ui/oauth2-redirect.js OAuth redirect helper for Swagger UI.
pkg/platform/swagger/swagger-ui/oauth2-redirect.html OAuth redirect HTML entrypoint for Swagger UI.
pkg/platform/swagger/swagger-ui/index.html Swagger UI index page.
pkg/platform/swagger/swagger-ui/index.css Swagger UI basic styling.
pkg/platform/swagger/swagger-ui/favicon-32x32.png Swagger UI favicon asset.
pkg/platform/swagger/swagger-ui/favicon-16x16.png Swagger UI favicon asset.
pkg/platform/middleware/auth.go Removes old chi middleware-based JWT authentication.
pkg/platform/handler/response.go Error envelope renamed to {"message": ...} to match OpenAPI schema.
pkg/platform/auth/auth.go Adds OpenAPI authentication hook + token parsing helper.
pkg/file/models.go Removes old response DTOs + conversion helpers.
pkg/file/handler.go Routes moved under /v1/*; uses generated request/response models + mapping helpers.
pkg/app/server.go Loads embedded OpenAPI spec; adds oapi request validator middleware; registers swagger routes.
pkg/api/generate.go Adds go:generate for oapi-codegen against openapi-spec.yaml.
pkg/api/codegen.yml oapi-codegen config: models + embedded spec.
pkg/api/api.gen.go Generated OpenAPI models + embedded spec loader.
openapi-spec.yaml New OpenAPI 3.0.3 contract for all endpoints.
go.mod Adds OpenAPI/middleware deps + tool directives; updates pgx.
go.sum Dependency checksum updates for new deps.
generate.go Switches to go tool sqlc generation (removes swag generation).
e2e/api/user.test.ts Updates user create request path (trailing slash removal).
e2e/api/auth.test.ts Updates token info assertion from sub to subject.
.gitignore Stops ignoring pkg/docs (swag-based docs removed).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

## Pull request overview This PR shifts the API to a schema-first approach by introducing an OpenAPI 3 spec, generating Go models from it, and validating incoming requests via OpenAPI middleware. It also replaces the previous swagger annotation-based approach and introduces a built-in Swagger UI for interactive documentation. **Changes:** - Add `openapi-spec.yaml` + `oapi-codegen` output (`pkg/api`) and wire request validation middleware into the server. - Refactor handlers to decode/encode via generated `api.*` types and use explicit mapping helpers. - Replace prior auth middleware with an OpenAPI `AuthenticationFunc` implementation and serve Swagger UI + spec endpoints. ### Reviewed changes Copilot reviewed 29 out of 39 changed files in this pull request and generated 12 comments. <details> <summary>Show a summary per file</summary> | File | Description | | ---- | ----------- | | pkg/version/models.go | Removes request tags/examples previously used for validator/swagger generation. | | pkg/version/handler.go | Routes moved under `/v1/*`; uses generated request/response models + mapping helpers. | | pkg/user/models.go | Removes request tags/examples and old response conversion helpers. | | pkg/user/handler.go | Drops router-level auth middleware usage; maps to/from generated models. | | pkg/project/models.go | Removes request tags/examples and old response conversion helpers. | | pkg/project/handler.go | Routes moved under `/v1/*`; uses generated request/response models + mapping helpers. | | pkg/platform/swagger/swagger.go | Adds endpoints to serve OpenAPI spec (JSON/YAML) and embedded Swagger UI. | | pkg/platform/swagger/swagger-ui/swagger-initializer.js | Swagger UI bootstrap pointing to `/swagger/doc.json`. | | pkg/platform/swagger/swagger-ui/oauth2-redirect.js | OAuth redirect helper for Swagger UI. | | pkg/platform/swagger/swagger-ui/oauth2-redirect.html | OAuth redirect HTML entrypoint for Swagger UI. | | pkg/platform/swagger/swagger-ui/index.html | Swagger UI index page. | | pkg/platform/swagger/swagger-ui/index.css | Swagger UI basic styling. | | pkg/platform/swagger/swagger-ui/favicon-32x32.png | Swagger UI favicon asset. | | pkg/platform/swagger/swagger-ui/favicon-16x16.png | Swagger UI favicon asset. | | pkg/platform/middleware/auth.go | Removes old chi middleware-based JWT authentication. | | pkg/platform/handler/response.go | Error envelope renamed to `{"message": ...}` to match OpenAPI schema. | | pkg/platform/auth/auth.go | Adds OpenAPI authentication hook + token parsing helper. | | pkg/file/models.go | Removes old response DTOs + conversion helpers. | | pkg/file/handler.go | Routes moved under `/v1/*`; uses generated request/response models + mapping helpers. | | pkg/app/server.go | Loads embedded OpenAPI spec; adds oapi request validator middleware; registers swagger routes. | | pkg/api/generate.go | Adds `go:generate` for `oapi-codegen` against `openapi-spec.yaml`. | | pkg/api/codegen.yml | `oapi-codegen` config: models + embedded spec. | | pkg/api/api.gen.go | Generated OpenAPI models + embedded spec loader. | | openapi-spec.yaml | New OpenAPI 3.0.3 contract for all endpoints. | | go.mod | Adds OpenAPI/middleware deps + tool directives; updates pgx. | | go.sum | Dependency checksum updates for new deps. | | generate.go | Switches to `go tool sqlc` generation (removes swag generation). | | e2e/api/user.test.ts | Updates user create request path (trailing slash removal). | | e2e/api/auth.test.ts | Updates token info assertion from `sub` to `subject`. | | .gitignore | Stops ignoring `pkg/docs` (swag-based docs removed). | </details> --- 💡 <a href="/DocPort-io/app/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.
@ -0,0 +1,882 @@
openapi: 3.0.3
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:47 +00:00

AttachFileToVersionRequest allows fileId minimum 0. The service/API expects positive IDs (gt=0), so the schema should use minimum: 1 to prevent invalid requests from passing OpenAPI validation.

AttachFileToVersionRequest allows `fileId` minimum 0. The service/API expects positive IDs (gt=0), so the schema should use `minimum: 1` to prevent invalid requests from passing OpenAPI validation.
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:47 +00:00

DetachFileFromVersionRequest allows fileId minimum 0. The service/API expects positive IDs (gt=0), so the schema should use minimum: 1 to match the previous validation rules.

DetachFileFromVersionRequest allows `fileId` minimum 0. The service/API expects positive IDs (gt=0), so the schema should use `minimum: 1` to match the previous validation rules.
@ -0,0 +399,4 @@
operationId: createUser
summary: Create a new user
tags:
- users
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:48 +00:00

POST /api/v1/users is missing a security requirement. With the OAPI validator middleware, this makes user creation unauthenticated, which differs from prior behavior (e2e tests still send a Bearer token). Add security: - OpenIdConnect: [] if this must remain protected.

POST /api/v1/users is missing a `security` requirement. With the OAPI validator middleware, this makes user creation unauthenticated, which differs from prior behavior (e2e tests still send a Bearer token). Add `security: - OpenIdConnect: []` if this must remain protected.
@ -0,0 +424,4 @@
operationId: getUserById
summary: Get a user by ID
tags:
- users
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:46 +00:00

GET /api/v1/users/{userId} is missing a security requirement, making it publicly accessible under the OAPI validator middleware. Add security: - OpenIdConnect: [] if user lookup should require authentication.

GET /api/v1/users/{userId} is missing a `security` requirement, making it publicly accessible under the OAPI validator middleware. Add `security: - OpenIdConnect: []` if user lookup should require authentication.
@ -0,0 +492,4 @@
schema:
type: integer
format: int64
example: 100
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:47 +00:00

QueryLimit schema doesn't express the 1..100 range enforced by ParsePagination (values outside are silently coerced to 100). Add minimum: 1 and maximum: 100 so invalid values are rejected at the OpenAPI validation layer.

QueryLimit schema doesn't express the 1..100 range enforced by ParsePagination (values outside are silently coerced to 100). Add `minimum: 1` and `maximum: 100` so invalid values are rejected at the OpenAPI validation layer.
@ -0,0 +502,4 @@
required: false
schema:
type: integer
format: int64
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:47 +00:00

QueryOffset schema doesn't express the non-negative constraint enforced by ParsePagination (negative offsets are coerced to 0). Add minimum: 0 so invalid values are rejected by OpenAPI validation instead of silently coerced.

QueryOffset schema doesn't express the non-negative constraint enforced by ParsePagination (negative offsets are coerced to 0). Add `minimum: 0` so invalid values are rejected by OpenAPI validation instead of silently coerced.
@ -0,0 +724,4 @@
projectId:
type: integer
format: int64
example: 1
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:48 +00:00

CreateVersionRequest does not constrain projectId to be > 0, but the previous request validation enforced gt=0. Add minimum: 1 for projectId so schema validation matches the service expectations.

CreateVersionRequest does not constrain `projectId` to be > 0, but the previous request validation enforced gt=0. Add `minimum: 1` for `projectId` so schema validation matches the service expectations.
@ -0,0 +859,4 @@
type: object
required:
- name
- email
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:48 +00:00

CreateUserRequest requires emailVerified. Previously this could be omitted and default to false (Go bool zero value). If you want backward compatibility, remove it from the required list and set a schema default of false.

CreateUserRequest requires `emailVerified`. Previously this could be omitted and default to false (Go bool zero value). If you want backward compatibility, remove it from the required list and set a schema default of false.
@ -0,0 +95,4 @@
token, err := jwt.ParseString(unverifiedToken, jwt.WithVerify(false))
if errors.Is(err, jwt.TokenExpiredError()) {
return errors.New("token expired"), TokenContext{}
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:49 +00:00

GetToken parses the JWT with signature verification disabled (jwt.WithVerify(false)). If this helper is ever called without a prior authenticated/validated request, forged tokens would be accepted and their claims trusted. Prefer verifying the token with JWKS here, or make the function explicitly unverified (naming/docs + keep usage tightly scoped).

GetToken parses the JWT with signature verification disabled (jwt.WithVerify(false)). If this helper is ever called without a prior authenticated/validated request, forged tokens would be accepted and their claims trusted. Prefer verifying the token with JWKS here, or make the function explicitly unverified (naming/docs + keep usage tightly scoped).
@ -0,0 +19,4 @@
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(openapi)
if err != nil {
log.Printf("failed to write swagger spec: %v", err)
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:49 +00:00

Swagger spec handlers write the status code before encoding and do not set a Content-Type. Consider setting Content-Type (application/json / application/yaml) and encoding first so you can still return a 500 on encoder failures instead of partially writing a 200 response.

Swagger spec handlers write the status code before encoding and do not set a Content-Type. Consider setting Content-Type (application/json / application/yaml) and encoding first so you can still return a 500 on encoder failures instead of partially writing a 200 response.
@ -42,19 +38,12 @@ func (h *Handler) RegisterRoutes(r chi.Router) {
})
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:48 +00:00

GetMe maps any auth.GetToken error to a 400 "invalid request payload" response. Missing/invalid Authorization should return an authentication error (typically 401), and the error message should reflect the token problem rather than the request body.

GetMe maps any auth.GetToken error to a 400 "invalid request payload" response. Missing/invalid Authorization should return an authentication error (typically 401), and the error message should reflect the token problem rather than the request body.
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026-03-15 15:38:48 +00:00

CreateUser responds with HTTP 200, but the OpenAPI spec for POST /api/v1/users declares a 201 response (and the other create handlers use StatusCreated). Align the handler status code with the contract (or update the spec if 200 is intended).

CreateUser responds with HTTP 200, but the OpenAPI spec for POST /api/v1/users declares a 201 response (and the other create handlers use StatusCreated). Align the handler status code with the contract (or update the spec if 200 is intended).
sonarqubecloud[bot] commented 2026-03-15 15:51:46 +00:00 (Migrated from github.com)
## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=DocPort-io_app-v2&pullRequest=3) **Quality Gate passed** Issues ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=DocPort-io_app-v2&pullRequest=3&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true) ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=DocPort-io_app-v2&pullRequest=3&issueStatuses=ACCEPTED) Measures ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=DocPort-io_app-v2&pullRequest=3&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true) ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=DocPort-io_app-v2&pullRequest=3&metric=new_coverage&view=list) ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=DocPort-io_app-v2&pullRequest=3&metric=new_duplicated_lines_density&view=list) [See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=DocPort-io_app-v2&pullRequest=3)
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
DocPort/app!3
No description provided.