diff --git a/.github/workflows/svelte-kit.yml b/.github/workflows/svelte-kit.yml index eb680743..b26ac0de 100644 --- a/.github/workflows/svelte-kit.yml +++ b/.github/workflows/svelte-kit.yml @@ -7,17 +7,37 @@ on: branches: [master] paths: - frontend/svelte-kit/** + - backend/spring-boot/src/main/java/** pull_request: branches: [master] paths: - frontend/svelte-kit/** + - backend/spring-boot/src/main/java/** env: PUBLIC_APP_NAME: ${{vars.PUBLIC_APP_NAME}} PUBLIC_API_URL: ${{vars.PUBLIC_API_URL}} jobs: + changes: + runs-on: ubuntu-latest + outputs: + frontend: ${{ steps.filter.outputs.frontend }} + backend: ${{ steps.filter.outputs.backend }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + frontend: + - 'frontend/svelte-kit/**' + backend: + - 'backend/spring-boot/src/main/java/**' + build: + needs: changes + if: needs.changes.outputs.frontend == 'true' || github.event_name == 'workflow_dispatch' defaults: run: working-directory: frontend/svelte-kit @@ -52,6 +72,87 @@ jobs: npx playwright install --with-deps npx playwright test --pass-with-no-tests + check-models: + needs: changes + if: needs.changes.outputs.backend == 'true' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + + env: + REDIS_PASSWORD: "" + + services: + postgres: + image: postgres:17 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: root + POSTGRES_DB: bugzkit + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + redis: + image: redis:7 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: "21" + distribution: "temurin" + cache: maven + + - name: Build Spring Boot + run: mvn -B clean install -DskipTests + working-directory: backend/spring-boot + + - name: Start Spring Boot + run: mvn spring-boot:run -Dspring-boot.run.profiles=dev & + working-directory: backend/spring-boot + + - name: Wait for backend + run: | + for i in {1..30}; do + curl -sf http://localhost:8080/v3/api-docs > /dev/null && echo "Backend ready" && exit 0 + echo "Waiting... ($i/30)" + sleep 5 + done + echo "Backend failed to start" && exit 1 + + - name: Set up Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Install frontend dependencies + run: pnpm i + working-directory: frontend/svelte-kit + + - name: Generate models + run: pnpm run generate:models + working-directory: frontend/svelte-kit + + - name: Check for model drift + run: git diff --exit-code frontend/svelte-kit/src/lib/models/api.d.ts + push-image: needs: build runs-on: ubuntu-latest diff --git a/backend/spring-boot/src/main/java/org/bugzkit/api/admin/controller/UserController.java b/backend/spring-boot/src/main/java/org/bugzkit/api/admin/controller/UserController.java index 337ddc71..a196ce37 100644 --- a/backend/spring-boot/src/main/java/org/bugzkit/api/admin/controller/UserController.java +++ b/backend/spring-boot/src/main/java/org/bugzkit/api/admin/controller/UserController.java @@ -1,5 +1,7 @@ package org.bugzkit.api.admin.controller; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.bugzkit.api.admin.payload.request.PatchUserRequest; import org.bugzkit.api.admin.payload.request.UserRequest; @@ -14,6 +16,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "admin") +@SecurityRequirement(name = "cookieAuth") @RestController("adminUserController") @RequestMapping(Path.ADMIN_USERS) public class UserController extends CrudController { diff --git a/backend/spring-boot/src/main/java/org/bugzkit/api/auth/controller/AuthController.java b/backend/spring-boot/src/main/java/org/bugzkit/api/auth/controller/AuthController.java index 4ebb1ac6..0ddd1be6 100644 --- a/backend/spring-boot/src/main/java/org/bugzkit/api/auth/controller/AuthController.java +++ b/backend/spring-boot/src/main/java/org/bugzkit/api/auth/controller/AuthController.java @@ -1,5 +1,7 @@ package org.bugzkit.api.auth.controller; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import java.util.List; @@ -30,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "auth") @RestController @RequestMapping(Path.AUTH) public class AuthController { @@ -74,6 +77,7 @@ public ResponseEntity deleteTokens(HttpServletRequest request) { return removeAuthCookies(); } + @SecurityRequirement(name = "cookieAuth") @GetMapping("/tokens/devices") public ResponseEntity> findAllDevices(HttpServletRequest request) { final var accessToken = AuthUtil.getValueFromCookie("accessToken", request); @@ -87,6 +91,7 @@ public ResponseEntity deleteTokensOnAllDevices() { return removeAuthCookies(); } + @SecurityRequirement(name = "cookieAuth") @DeleteMapping("/tokens/devices/{deviceId}") public ResponseEntity revokeDevice(@PathVariable String deviceId) { deviceService.revoke(deviceId); diff --git a/backend/spring-boot/src/main/java/org/bugzkit/api/shared/config/SpringDocConfig.java b/backend/spring-boot/src/main/java/org/bugzkit/api/shared/config/SpringDocConfig.java new file mode 100644 index 00000000..c6e115d7 --- /dev/null +++ b/backend/spring-boot/src/main/java/org/bugzkit/api/shared/config/SpringDocConfig.java @@ -0,0 +1,50 @@ +package org.bugzkit.api.shared.config; + +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.bugzkit.api.shared.error.ErrorMessage; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringDocConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info( + new Info() + .title("bugzkit") + .description("A production-ready web application template") + .version("1.0.0") + .contact(new Contact().email("office@bugzkit.com")) + .license( + new License() + .name("MIT License") + .url("https://choosealicense.com/licenses/mit/"))) + .components( + new Components() + .addSecuritySchemes( + "cookieAuth", + new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.COOKIE) + .name("accessToken"))); + } + + @Bean + public OpenApiCustomizer errorMessageSchema() { + return openApi -> + openApi + .getComponents() + .getSchemas() + .putAll(ModelConverters.getInstance().readAll(new AnnotatedType(ErrorMessage.class))); + } +} diff --git a/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/ProfileController.java b/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/ProfileController.java index e4902fd7..65bdee57 100644 --- a/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/ProfileController.java +++ b/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/ProfileController.java @@ -1,5 +1,7 @@ package org.bugzkit.api.user.controller; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.bugzkit.api.shared.constants.Path; import org.bugzkit.api.shared.ratelimit.RateLimit; @@ -15,6 +17,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "profile") +@SecurityRequirement(name = "cookieAuth") @RestController @RequestMapping(Path.PROFILE) public class ProfileController { diff --git a/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/RoleController.java b/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/RoleController.java index 9748c084..8fb2301e 100644 --- a/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/RoleController.java +++ b/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/RoleController.java @@ -1,5 +1,7 @@ package org.bugzkit.api.user.controller; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import org.bugzkit.api.shared.constants.Path; import org.bugzkit.api.user.payload.dto.RoleDTO; @@ -9,6 +11,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "roles") +@SecurityRequirement(name = "cookieAuth") @RestController @RequestMapping(Path.ROLES) public class RoleController { diff --git a/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/UserController.java b/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/UserController.java index a6106f1e..98b9a1c4 100644 --- a/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/UserController.java +++ b/backend/spring-boot/src/main/java/org/bugzkit/api/user/controller/UserController.java @@ -1,5 +1,6 @@ package org.bugzkit.api.user.controller; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.bugzkit.api.shared.constants.Path; import org.bugzkit.api.shared.payload.dto.AvailabilityDTO; @@ -18,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "users") @RestController @RequestMapping(Path.USERS) public class UserController { diff --git a/backend/spring-boot/src/main/resources/application.yml b/backend/spring-boot/src/main/resources/application.yml index 7389001b..b1d26136 100644 --- a/backend/spring-boot/src/main/resources/application.yml +++ b/backend/spring-boot/src/main/resources/application.yml @@ -52,7 +52,3 @@ jwt: rate-limit: enabled: true - -springdoc: - swagger-ui: - url: /openapi.yml diff --git a/backend/spring-boot/src/main/resources/static/openapi.yml b/backend/spring-boot/src/main/resources/static/openapi.yml deleted file mode 100644 index 0411473a..00000000 --- a/backend/spring-boot/src/main/resources/static/openapi.yml +++ /dev/null @@ -1,1144 +0,0 @@ -openapi: 3.0.3 -info: - title: bugzkit - description: A production-ready web application template - termsOfService: https://swagger.io/terms/ - contact: - email: office@bugzkit.com - license: - name: MIT License - url: https://choosealicense.com/licenses/mit/ - version: 1.0.0 -servers: - - url: http://localhost:8080 - - url: https://localhost:8080 -tags: - - name: auth - description: Authentication endpoints - - name: profile - description: Profile endpoints - - name: users - description: Users endpoints - - name: roles - description: Roles endpoints - - name: admin - description: Admin endpoints -paths: - /auth/register: - post: - tags: - - auth - summary: User registration - requestBody: - $ref: "#/components/requestBodies/RegisterUserRequest" - responses: - 201: - description: User registered successfully - content: - application/json: - schema: - $ref: "#/components/schemas/UserDTO" - example: - id: 5 - username: john.doe - email: john.doe@localhost - createdAt: 2023-05-18T00:51:50.758738 - 400: - $ref: "#/components/responses/BadRequest" - 409: - $ref: "#/components/responses/Conflict" - 429: - $ref: "#/components/responses/TooManyRequests" - /auth/tokens: - post: - tags: - - auth - summary: Create access and refresh tokens - requestBody: - $ref: "#/components/requestBodies/AuthTokenRequest" - responses: - 204: - description: Auth tokens created successfully - headers: - Set-Cookie: - schema: - type: array - items: - type: string - example: - - "accessToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlcyI6WyJVU0VSIl0sImlzc3VlZEF0IjoiMjAyMS0xMC0yMFQxNDozMzo0NC43MDcyOTMzMDlaIiwiZXhwIjoxNjM0NzQxMzI0LCJ1c2VySWQiOjJ9.uXOVA1q-o2DtHmwBAzEfqEm8GLpAhXrYo0rlZ_6NFbBGILhkV74x-Iu9W2uSfSlwp1IfKPCHlR6zWVPvAbhWVw; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=9000" - - "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlcyI6WyJVU0VSIl0sImlzc3VlZEF0IjoiMjAyMS0xMC0xOVQxMzowMDowMy45MDYzNDQyMDJaIiwiZXhwIjoxNjM1MjUzMjAzLCJ1c2VySWQiOjJ9.RHzh6qyGJEKYdvCuCF7wPoUGBSrDGeoY8dSTBhuv21Fzw_CPEa5KeI3MOYgSN3zA1o_ZlKwjHgpSsPM3xAO_DQ; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=604800" - 400: - $ref: "#/components/responses/BadRequest" - 401: - $ref: "#/components/responses/Unauthorized" - 403: - $ref: "#/components/responses/Forbidden" - 429: - $ref: "#/components/responses/TooManyRequests" - delete: - tags: - - auth - summary: Delete access and refresh tokens - responses: - 204: - description: Access and refresh tokens deleted successfully - headers: - Set-Cookie: - schema: - type: array - items: - type: string - example: - - "accessToken=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0" - - "refreshToken=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0" - /auth/tokens/devices: - get: - tags: - - auth - summary: Get all logged-in devices - security: - - cookieAuth: [ ] - responses: - 200: - description: Devices retrieved successfully - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/DeviceDTO" - example: - - deviceId: "550e8400-e29b-41d4-a716-446655440000" - userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" - createdAt: "2024-01-15T10:30:00" - lastActiveAt: "2024-01-15T12:00:00" - current: true - - deviceId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8" - userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)" - createdAt: "2024-01-14T08:00:00" - lastActiveAt: "2024-01-14T20:00:00" - current: false - 401: - $ref: "#/components/responses/Unauthorized" - delete: - tags: - - auth - summary: Delete access and refresh tokens on all devices - responses: - 204: - description: Access and refresh tokens deleted successfully on all devices - headers: - Set-Cookie: - schema: - type: array - items: - type: string - example: - - "accessToken=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0" - - "refreshToken=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0" - /auth/tokens/devices/{deviceId}: - delete: - tags: - - auth - summary: Delete a specific device session - security: - - cookieAuth: [ ] - parameters: - - name: deviceId - in: path - description: Device ID to delete - required: true - schema: - type: string - example: "550e8400-e29b-41d4-a716-446655440000" - responses: - 204: - description: Device session deleted successfully - 401: - $ref: "#/components/responses/Unauthorized" - /auth/tokens/refresh: - post: - tags: - - auth - summary: Refresh auth tokens - parameters: - - in: cookie - name: refreshToken - schema: - type: string - example: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlcyI6WyJVU0VSIl0sImlzc3VlZEF0IjoiMjAyMS0xMC0xOVQxMzowMDowMy45MDYzNDQyMDJaIiwiZXhwIjoxNjM1MjUzMjAzLCJ1c2VySWQiOjJ9.RHzh6qyGJEKYdvCuCF7wPoUGBSrDGeoY8dSTBhuv21Fzw_CPEa5KeI3MOYgSN3zA1o_ZlKwjHgpSsPM3xAO_DQ" - responses: - 204: - description: Auth tokens refreshed successfully - headers: - Set-Cookie: - schema: - type: array - items: - type: string - example: - - "accessToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlcyI6WyJVU0VSIl0sImlzc3VlZEF0IjoiMjAyMS0xMC0yMFQxNDozMzo0NC43MDcyOTMzMDlaIiwiZXhwIjoxNjM0NzQxMzI0LCJ1c2VySWQiOjJ9.uXOVA1q-o2DtHmwBAzEfqEm8GLpAhXrYo0rlZ_6NFbBGILhkV74x-Iu9W2uSfSlwp1IfKPCHlR6zWVPvAbhWVw; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=9000" - - "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJyb2xlcyI6WyJVU0VSIl0sImlzc3VlZEF0IjoiMjAyMS0xMC0xOVQxMzowMDowMy45MDYzNDQyMDJaIiwiZXhwIjoxNjM1MjUzMjAzLCJ1c2VySWQiOjJ9.RHzh6qyGJEKYdvCuCF7wPoUGBSrDGeoY8dSTBhuv21Fzw_CPEa5KeI3MOYgSN3zA1o_ZlKwjHgpSsPM3xAO_DQ; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=604800" - 400: - $ref: "#/components/responses/BadRequest" - 429: - $ref: "#/components/responses/TooManyRequests" - /auth/password/forgot: - post: - tags: - - auth - summary: Send forgot password email - requestBody: - $ref: "#/components/requestBodies/ForgotPasswordRequest" - responses: - 204: - description: Forgot password email sent successfully - 429: - $ref: "#/components/responses/TooManyRequests" - /auth/password/reset: - post: - tags: - - auth - summary: Reset password - requestBody: - $ref: "#/components/requestBodies/ResetPasswordRequest" - responses: - 204: - description: Password reset successfully - 400: - $ref: "#/components/responses/BadRequest" - 429: - $ref: "#/components/responses/TooManyRequests" - /auth/verification-email: - post: - tags: - - auth - summary: Send verification email - requestBody: - $ref: "#/components/requestBodies/VerificationEmailRequest" - responses: - 204: - description: Verification email sent successfully - 429: - $ref: "#/components/responses/TooManyRequests" - /auth/verify-email: - post: - tags: - - auth - summary: Verify email - requestBody: - $ref: "#/components/requestBodies/VerifyEmailRequest" - responses: - 204: - description: Email verified successfully - 400: - $ref: "#/components/responses/BadRequest" - 429: - $ref: "#/components/responses/TooManyRequests" - /profile: - get: - tags: - - profile - summary: Get profile - security: - - cookieAuth: [ ] - responses: - 200: - description: Profile retrieved successfully - content: - application/json: - schema: - $ref: "#/components/schemas/UserDTO" - example: - id: 2 - username: user - email: user@localhost - createdAt: 2023-05-18T00:51:50.758738 - 401: - $ref: "#/components/responses/Unauthorized" - patch: - tags: - - profile - summary: Patch profile - security: - - cookieAuth: [ ] - requestBody: - $ref: "#/components/requestBodies/PatchProfileRequest" - responses: - 200: - description: Profile patched successfully - content: - application/json: - schema: - $ref: "#/components/schemas/UserDTO" - example: - id: 2 - username: john.doe - email: john.doe@localhost - createdAt: 2023-05-18T00:51:50.758738 - 400: - $ref: "#/components/responses/BadRequest" - 401: - $ref: "#/components/responses/Unauthorized" - 409: - $ref: "#/components/responses/Conflict" - delete: - tags: - - profile - summary: Delete profile - security: - - cookieAuth: [ ] - responses: - 204: - description: Profile deleted successfully - 401: - $ref: "#/components/responses/Unauthorized" - /profile/password: - patch: - tags: - - profile - summary: Change profile password - security: - - cookieAuth: [ ] - requestBody: - $ref: "#/components/requestBodies/ChangePasswordRequest" - responses: - 204: - description: Profile password changed successfully - 400: - $ref: "#/components/responses/BadRequest" - 401: - $ref: "#/components/responses/Unauthorized" - 429: - $ref: "#/components/responses/TooManyRequests" - /users: - get: - tags: - - users - summary: Get users with pagination - parameters: - - $ref: "#/components/parameters/page" - - $ref: "#/components/parameters/size" - - $ref: "#/components/parameters/sort" - responses: - 200: - description: Users retrieved successfully - content: - application/json: - schema: - type: object - required: - - "data" - - "total" - properties: - data: - type: array - items: - $ref: "#/components/schemas/UserDTO" - total: - type: integer - example: - data: - - id: 1 - username: admin - email: null - createdAt: 2023-05-18T00:51:50.758738 - - id: 2 - username: user - email: null - createdAt: 2023-05-18T00:51:50.758738 - - id: 3 - username: john.doe - email: null - createdAt: 2023-05-18T00:51:50.758738 - total: 3 - /users/{id}: - get: - tags: - - users - summary: Get user by id - parameters: - - $ref: "#/components/parameters/id" - responses: - 200: - description: User retrieved successfully - content: - application/json: - schema: - $ref: "#/components/schemas/UserDTO" - example: - id: 2 - username: user - email: null - createdAt: 2023-05-18T00:51:50.758738 - 404: - $ref: "#/components/responses/NotFound" - /users/username/{username}: - get: - tags: - - users - summary: Get user by username - parameters: - - $ref: "#/components/parameters/username" - responses: - 200: - description: User retrieved successfully - content: - application/json: - schema: - $ref: "#/components/schemas/UserDTO" - example: - id: 2 - username: user - email: null - createdAt: 2023-05-18T00:51:50.758738 - 404: - $ref: "#/components/responses/NotFound" - /users/username/availability: - post: - tags: - - users - summary: Check username availability - requestBody: - $ref: "#/components/requestBodies/UsernameAvailabilityRequest" - responses: - 200: - description: Username availability checked successfully - content: - application/json: - schema: - $ref: "#/components/schemas/AvailabilityDTO" - example: - available: true - 400: - $ref: "#/components/responses/BadRequest" - 429: - $ref: "#/components/responses/TooManyRequests" - /users/email/availability: - post: - tags: - - users - summary: Check email availability - requestBody: - $ref: "#/components/requestBodies/EmailAvailabilityRequest" - responses: - 200: - description: Email availability checked successfully - content: - application/json: - schema: - $ref: "#/components/schemas/AvailabilityDTO" - example: - available: true - 400: - $ref: "#/components/responses/BadRequest" - 429: - $ref: "#/components/responses/TooManyRequests" - /roles: - get: - tags: - - roles - summary: Get roles - security: - - cookieAuth: [ ] - responses: - 200: - description: Roles retrieved successfully - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/RoleDTO" - example: - - name: ADMIN - - name: USER - 401: - $ref: "#/components/responses/Unauthorized" - 403: - $ref: "#/components/responses/Forbidden" - /admin/users: - post: - tags: - - admin - summary: Create user - security: - - cookieAuth: [ ] - requestBody: - $ref: "#/components/requestBodies/UserRequest" - responses: - 201: - description: User created successfully - content: - application/json: - schema: - $ref: "#/components/schemas/UserDTO" - example: - id: 5 - username: john.doe - email: john.doe@localhost - active: true - lock: false - createdAt: 2023-05-18T00:51:50.758738 - roles: - - name: USER - 400: - $ref: "#/components/responses/BadRequest" - 401: - $ref: "#/components/responses/Unauthorized" - 403: - $ref: "#/components/responses/Forbidden" - 409: - $ref: "#/components/responses/Conflict" - get: - tags: - - admin - summary: Get users with pagination - security: - - cookieAuth: [ ] - parameters: - - $ref: "#/components/parameters/page" - - $ref: "#/components/parameters/size" - - $ref: "#/components/parameters/sort" - responses: - 200: - description: Users retrieved successfully - content: - application/json: - schema: - type: object - required: - - "data" - - "total" - properties: - data: - type: array - items: - $ref: "#/components/schemas/UserDTO" - total: - type: integer - example: - data: - - id: 1 - username: admin - email: admin@localhost - active: true - lock: false - createdAt: 2023-05-18T00:51:50.758738 - roles: - - name: USER - - name: ADMIN - - id: 2 - username: user - email: user@localhost - active: true - lock: false - createdAt: 2023-05-18T00:51:50.758738 - roles: - - name: USER - - id: 3 - username: john.doe - email: john.doe@localhost - active: true - lock: false - createdAt: 2023-05-18T00:51:50.758738 - roles: - - name: USER - total: 3 - 401: - $ref: "#/components/responses/Unauthorized" - 403: - $ref: "#/components/responses/Forbidden" - /admin/users/{id}: - get: - tags: - - admin - summary: Get user by id - security: - - cookieAuth: [ ] - parameters: - - $ref: "#/components/parameters/id" - responses: - 200: - description: User retrieved successfully - content: - application/json: - schema: - $ref: "#/components/schemas/UserDTO" - example: - id: 2 - username: user - email: user@localhost - active: true - lock: false - createdAt: 2023-05-18T00:51:50.758738 - roles: - - name: USER - 401: - $ref: "#/components/responses/Unauthorized" - 403: - $ref: "#/components/responses/Forbidden" - 404: - $ref: "#/components/responses/NotFound" - put: - tags: - - admin - summary: Update user by id - security: - - cookieAuth: [ ] - parameters: - - $ref: "#/components/parameters/id" - requestBody: - $ref: "#/components/requestBodies/UserRequest" - responses: - 200: - description: User updated successfully - content: - application/json: - schema: - $ref: "#/components/schemas/UserDTO" - example: - id: 2 - username: john.doe - email: john.doe@localhost - active: true - lock: false - createdAt: 2023-05-18T00:51:50.758738 - roles: - - name: USER - - name: ADMIN - 400: - $ref: "#/components/responses/BadRequest" - 401: - $ref: "#/components/responses/Unauthorized" - 403: - $ref: "#/components/responses/Forbidden" - 409: - $ref: "#/components/responses/Conflict" - patch: - tags: - - admin - summary: Patch user by id - security: - - cookieAuth: [ ] - parameters: - - $ref: "#/components/parameters/id" - requestBody: - $ref: "#/components/requestBodies/PatchUserRequest" - responses: - 200: - description: User patched successfully - content: - application/json: - schema: - $ref: "#/components/schemas/UserDTO" - example: - id: 2 - username: john.doe - email: john.doe@localhost - active: true - lock: false - createdAt: 2023-05-18T00:51:50.758738 - roles: - - name: USER - - name: ADMIN - 400: - $ref: "#/components/responses/BadRequest" - 401: - $ref: "#/components/responses/Unauthorized" - 403: - $ref: "#/components/responses/Forbidden" - 404: - $ref: "#/components/responses/NotFound" - 409: - $ref: "#/components/responses/Conflict" - delete: - tags: - - admin - summary: Delete user by id - security: - - cookieAuth: [ ] - parameters: - - $ref: "#/components/parameters/id" - responses: - 204: - description: User deleted successfully - 401: - $ref: "#/components/responses/Unauthorized" - 403: - $ref: "#/components/responses/Forbidden" -components: - responses: - BadRequest: - description: Bad Request - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ErrorMessage" - example: - timestamp: 2023-05-18T00:51:50.758738 - status: 400 - error: Bad Request - codes: - - API_ERROR_X_REQUIRED - - API_ERROR_X_INVALID - Unauthorized: - description: Unauthorized - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ErrorMessage" - example: - timestamp: 2023-05-18T00:51:50.758738 - status: 401 - error: Unauthorized - codes: - - API_ERROR_AUTH_UNAUTHORIZED - Forbidden: - description: Forbidden - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ErrorMessage" - example: - timestamp: 2023-05-18T00:51:50.758738 - status: 403 - error: Forbidden - codes: - - API_ERROR_AUTH_FORBIDDEN - NotFound: - description: Not Found - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ErrorMessage" - example: - timestamp: 2023-05-18T00:51:50.758738 - status: 404 - error: Not Found - codes: - - API_ERROR_X_NOT_FOUND - Conflict: - description: Conflict - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ErrorMessage" - example: - timestamp: 2023-05-18T00:51:50.758738 - status: 409 - error: Conflict - codes: - - API_ERROR_X_EXISTS - TooManyRequests: - description: Too Many Requests - headers: - Retry-After: - schema: - type: integer - description: Seconds until the rate limit resets - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ErrorMessage" - example: - timestamp: 2023-05-18T00:51:50.758738 - status: 429 - error: Too Many Requests - codes: - - API_ERROR_REQUEST_TOO_MANY_REQUESTS - parameters: - id: - name: id - in: path - description: Resource id - required: true - schema: - type: number - example: 2 - username: - name: username - in: path - description: Username - required: true - schema: - type: string - example: user - page: - name: page - in: query - description: Page number - required: false - schema: - type: integer - default: 1 - size: - name: size - in: query - description: Number of items per page - required: false - schema: - type: integer - default: 10 - sort: - name: sort - in: query - description: Sort by field - required: false - schema: - type: string - default: unsorted - example: id,desc - requestBodies: - RegisterUserRequest: - description: Register user request - required: true - content: - application/json: - schema: - type: object - required: - - username - - email - - password - - confirmPassword - properties: - username: - type: string - email: - type: string - format: email - password: - type: string - confirmPassword: - type: string - example: - username: John.doe - email: john.doe@localhost - password: qwerty123 - confirmPassword: qwerty123 - AuthTokenRequest: - description: Auth token request - required: true - content: - application/json: - schema: - type: object - required: - - usernameOrEmail - - password - properties: - usernameOrEmail: - type: string - password: - type: string - example: - usernameOrEmail: user - password: qwerty123 - ForgotPasswordRequest: - description: Forgot password request - required: true - content: - application/json: - schema: - type: object - required: - - email - properties: - email: - type: string - format: email - example: - email: user@localhost - ResetPasswordRequest: - description: Reset password request - required: true - content: - application/json: - schema: - type: object - required: - - token - - password - - confirmPassword - properties: - token: - type: string - password: - type: string - confirmPassword: - type: string - example: - token: 550e8400-e29b-41d4-a716-446655440000 - password: qwerty321 - confirmPassword: qwerty321 - VerificationEmailRequest: - description: Verification email request - required: true - content: - application/json: - schema: - type: object - required: - - usernameOrEmail - properties: - usernameOrEmail: - type: string - example: - usernameOrEmail: user@localhost - VerifyEmailRequest: - description: Verify email request - required: true - content: - application/json: - schema: - type: object - required: - - token - properties: - token: - type: string - example: - token: 550e8400-e29b-41d4-a716-446655440000 - PatchProfileRequest: - description: Patch profile request - required: true - content: - application/json: - schema: - type: object - properties: - username: - type: string - email: - type: string - format: email - example: - username: John.doe - email: john.doe@localhost - ChangePasswordRequest: - description: Change password request - required: true - content: - application/json: - schema: - type: object - required: - - currentPassword - - newPassword - - confirmNewPassword - properties: - currentPassword: - type: string - newPassword: - type: string - confirmNewPassword: - type: string - example: - currentPassword: qwerty123 - newPassword: qwerty321 - confirmNewPassword: qwerty321 - UserRequest: - description: User request - required: true - content: - application/json: - schema: - type: object - required: - - username - - email - - password - - confirmPassword - - active - - lock - - roleNames - properties: - username: - type: string - email: - type: string - format: email - password: - type: string - confirmPassword: - type: string - active: - type: boolean - lock: - type: boolean - roleNames: - type: array - items: - type: string - enum: - - USER - - ADMIN - example: - username: John.doe - email: john.doe@localhost - password: qwerty123 - confirmPassword: qwerty123 - active: true - lock: false - roleNames: - - USER - PatchUserRequest: - description: Patch user request - required: true - content: - application/json: - schema: - type: object - properties: - username: - type: string - email: - type: string - format: email - password: - type: string - confirmPassword: - type: string - active: - type: boolean - lock: - type: boolean - roleNames: - type: array - items: - type: string - enum: - - USER - - ADMIN - example: - username: John.doe - email: john.doe@localhost - active: true - password: qwerty123 - confirmPassword: qwerty123 - lock: false - roleNames: - - USER - UsernameAvailabilityRequest: - description: Username availability request - required: true - content: - application/json: - schema: - type: object - required: - - username - properties: - username: - type: string - example: - username: user - EmailAvailabilityRequest: - description: Email availability request - required: true - content: - application/json: - schema: - type: object - required: - - email - properties: - email: - type: string - format: email - example: - email: user@localhost - schemas: - UserDTO: - type: object - required: - - id - - username - - email - - createdAt - properties: - id: - type: integer - format: int64 - username: - type: string - email: - type: string - format: email - nullable: true - active: - type: boolean - lock: - type: boolean - createdAt: - type: string - format: date-time - roles: - type: array - items: - $ref: "#/components/schemas/RoleDTO" - RoleDTO: - type: object - required: - - name - properties: - name: - type: string - enum: - - USER - - ADMIN - DeviceDTO: - type: object - required: - - deviceId - - createdAt - - lastActiveAt - - current - properties: - deviceId: - type: string - userAgent: - type: string - nullable: true - createdAt: - type: string - format: date-time - lastActiveAt: - type: string - format: date-time - current: - type: boolean - AvailabilityDTO: - type: object - required: - - available - properties: - available: - type: boolean - ErrorMessage: - type: object - required: - - timestamp - - status - - error - - codes - properties: - timestamp: - type: string - format: date-time - status: - type: integer - format: int32 - error: - type: string - codes: - type: array - items: - type: string - securitySchemes: - cookieAuth: - type: apiKey - in: cookie - name: accessToken diff --git a/backend/spring-boot/src/test/resources/application.yml b/backend/spring-boot/src/test/resources/application.yml index 00ec1ba4..4d71fb1f 100644 --- a/backend/spring-boot/src/test/resources/application.yml +++ b/backend/spring-boot/src/test/resources/application.yml @@ -76,6 +76,3 @@ jwt: rate-limit: enabled: false -springdoc: - swagger-ui: - url: /openapi.yml diff --git a/docs/src/content/getting-started/using.mdx b/docs/src/content/getting-started/using.mdx index bd280cf9..e3013492 100644 --- a/docs/src/content/getting-started/using.mdx +++ b/docs/src/content/getting-started/using.mdx @@ -80,3 +80,14 @@ pnpm run dev ``` You're all set! + +## Regenerating Frontend Models + +Frontend TypeScript models are auto-generated from the backend OpenAPI spec. Whenever you make changes to backend DTOs or endpoints, regenerate the models by running the backend first, then: + +```bash +cd frontend/svelte-kit +pnpm run generate:models +``` + +This fetches the live spec from `http://localhost:8080/v3/api-docs` and updates `src/lib/models/api.d.ts`. diff --git a/frontend/svelte-kit/.prettierignore b/frontend/svelte-kit/.prettierignore index 283f41a3..7cdc20c8 100644 --- a/frontend/svelte-kit/.prettierignore +++ b/frontend/svelte-kit/.prettierignore @@ -5,4 +5,5 @@ yarn.lock .vscode/ project.inlang/ -src/lib/paraglide/ \ No newline at end of file +src/lib/paraglide/ +src/lib/models/api.d.ts \ No newline at end of file diff --git a/frontend/svelte-kit/package.json b/frontend/svelte-kit/package.json index 792015c2..aaba45a0 100644 --- a/frontend/svelte-kit/package.json +++ b/frontend/svelte-kit/package.json @@ -12,6 +12,7 @@ "test:integration": "playwright test --pass-with-no-tests", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "generate:models": "openapi-typescript http://localhost:8080/v3/api-docs -o src/lib/models/api.d.ts", "check-updates": "npx npm-check-updates", "format": "prettier --write .", "lint": "prettier --check . && eslint .", @@ -47,6 +48,7 @@ "jsonwebtoken": "9.0.3", "lucide-svelte": "0.577.0", "mode-watcher": "1.1.0", + "openapi-typescript": "7.13.0", "postcss": "8.5.8", "prettier": "3.8.1", "prettier-plugin-svelte": "3.5.1", diff --git a/frontend/svelte-kit/pnpm-lock.yaml b/frontend/svelte-kit/pnpm-lock.yaml index 38cabaa0..5cefc05a 100644 --- a/frontend/svelte-kit/pnpm-lock.yaml +++ b/frontend/svelte-kit/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: mode-watcher: specifier: 1.1.0 version: 1.1.0(svelte@5.53.7) + openapi-typescript: + specifier: 7.13.0 + version: 7.13.0(typescript@5.9.3) postcss: specifier: 8.5.8 version: 8.5.8 @@ -146,6 +149,14 @@ packages: '@ark/util@0.56.0': resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} @@ -432,6 +443,16 @@ packages: '@poppinss/macroable@1.1.0': resolution: {integrity: sha512-y/YKzZDuG8XrpXpM7Z1RdQpiIc0MAKyva24Ux1PB4aI7RiSI/79K8JVDcdyubriTm7vJ1LhFs8CrZpmPnx/8Pw==} + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.10': + resolution: {integrity: sha512-XCBR/9WHJ0cpezuunHMZjuFMl4KqUo7eiFwzrQrvm7lTXt0EBd3No8UY+9OyzXpDfreGEMMtxmaLZ+ksVw378g==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@rollup/plugin-commonjs@29.0.0': resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} @@ -940,9 +961,20 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.1: resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} engines: {node: '>= 0.4'} @@ -964,6 +996,9 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -975,6 +1010,9 @@ packages: '@internationalized/date': ^3.8.1 svelte: ^5.33.0 + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.4: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} @@ -990,6 +1028,9 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -1001,6 +1042,9 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -1262,6 +1306,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-id@4.1.3: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true @@ -1278,6 +1326,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -1315,9 +1367,20 @@ packages: joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-sha256@0.11.1: resolution: {integrity: sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1328,6 +1391,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1492,6 +1558,10 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + mode-watcher@1.1.0: resolution: {integrity: sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g==} peerDependencies: @@ -1523,6 +1593,12 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1535,6 +1611,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1566,6 +1646,10 @@ packages: engines: {node: '>=18'} hasBin: true + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + postcss-load-config@3.1.4: resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} @@ -1682,6 +1766,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} @@ -1768,6 +1856,10 @@ packages: resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} engines: {node: '>=14.0.0'} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -1897,6 +1989,10 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typebox@1.1.5: resolution: {integrity: sha512-TBdiM4mSppvWdmRDK5PoocxrMOqGIU9TxmS9zdHH+k8S/+2SIaNlPfMlx3f6hISxma14t2yX7SRySg7+TYYT9w==} @@ -1926,6 +2022,9 @@ packages: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2054,10 +2153,17 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2086,6 +2192,14 @@ snapshots: '@ark/util@0.56.0': optional: true + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/runtime@7.28.6': optional: true @@ -2177,7 +2291,7 @@ snapshots: '@eslint/config-array@0.23.2': dependencies: '@eslint/object-schema': 3.0.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 10.2.4 transitivePeerDependencies: - supports-color @@ -2310,6 +2424,29 @@ snapshots: '@poppinss/macroable@1.1.0': optional: true + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.10(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.9 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@rollup/plugin-commonjs@29.0.0(rollup@4.59.0)': dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.59.0) @@ -2643,7 +2780,7 @@ snapshots: '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.56.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 10.0.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -2653,7 +2790,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) '@typescript-eslint/types': 8.56.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -2672,7 +2809,7 @@ snapshots: '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) '@typescript-eslint/utils': 8.56.1(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 10.0.2(jiti@2.6.1) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 @@ -2687,7 +2824,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) '@typescript-eslint/types': 8.56.1 '@typescript-eslint/visitor-keys': 8.56.1 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 10.2.4 semver: 7.7.4 tinyglobby: 0.2.15 @@ -2777,6 +2914,8 @@ snapshots: acorn@8.16.0: {} + agent-base@7.1.4: {} + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -2784,6 +2923,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-colors@4.1.3: {} + + argparse@2.0.1: {} + aria-query@5.3.1: {} arkregex@0.0.5: @@ -2804,6 +2947,8 @@ snapshots: axobject-query@4.1.0: {} + balanced-match@1.0.2: {} + balanced-match@4.0.4: {} bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.7)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)))(svelte@5.53.7)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)))(svelte@5.53.7): @@ -2819,6 +2964,10 @@ snapshots: transitivePeerDependencies: - '@sveltejs/kit' + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.4: dependencies: balanced-match: 4.0.4 @@ -2830,6 +2979,8 @@ snapshots: chai@6.2.2: {} + change-case@5.4.4: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -2843,6 +2994,8 @@ snapshots: clsx@2.1.1: {} + colorette@1.4.0: {} + commander@11.1.0: {} comment-json@4.6.2: @@ -2867,9 +3020,11 @@ snapshots: dayjs@1.11.19: optional: true - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 dedent@1.5.1: {} @@ -2990,7 +3145,7 @@ snapshots: '@types/estree': 1.0.8 ajv: 6.14.0 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 9.1.1 eslint-visitor-keys: 5.0.1 @@ -3112,6 +3267,13 @@ snapshots: dependencies: function-bind: 1.1.2 + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + human-id@4.1.3: {} ignore@5.3.2: {} @@ -3120,6 +3282,8 @@ snapshots: imurmurhash@0.1.4: {} + index-to-position@1.2.0: {} + inline-style-parser@0.2.7: {} is-core-module@2.16.1: @@ -3157,8 +3321,16 @@ snapshots: '@sideway/pinpoint': 2.0.0 optional: true + js-levenshtein@1.1.6: {} + js-sha256@0.11.1: {} + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + json-buffer@3.0.1: {} json-schema-to-ts@3.1.1: @@ -3169,6 +3341,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -3302,6 +3476,10 @@ snapshots: dependencies: brace-expansion: 5.0.4 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.2 + mode-watcher@1.1.0(svelte@5.53.7): dependencies: runed: 0.25.0(svelte@5.53.7) @@ -3323,6 +3501,16 @@ snapshots: obug@2.1.1: {} + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.10(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3340,6 +3528,12 @@ snapshots: dependencies: p-limit: 3.1.0 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.0 + index-to-position: 1.2.0 + type-fest: 4.41.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -3360,6 +3554,8 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + pluralize@8.0.0: {} + postcss-load-config@3.1.4(postcss@8.5.8): dependencies: lilconfig: 2.1.0 @@ -3411,6 +3607,8 @@ snapshots: readdirp@4.1.2: {} + require-from-string@2.0.2: {} + resolve@1.22.11: dependencies: is-core-module: 2.16.1 @@ -3514,6 +3712,8 @@ snapshots: superstruct@2.0.2: optional: true + supports-color@10.2.2: {} + supports-preserve-symlinks-flag@1.0.0: {} svelte-check@4.4.4(picomatch@4.0.3)(svelte@5.53.7)(typescript@5.9.3): @@ -3667,6 +3867,8 @@ snapshots: type-fest@2.19.0: optional: true + type-fest@4.41.0: {} + typebox@1.1.5: optional: true @@ -3700,6 +3902,8 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 + uri-js-replace@1.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -3788,8 +3992,12 @@ snapshots: word-wrap@1.2.5: {} + yaml-ast-parser@0.0.43: {} + yaml@1.10.2: {} + yargs-parser@21.1.1: {} + yocto-queue@0.1.0: {} yup@1.7.1: diff --git a/frontend/svelte-kit/src/error.html b/frontend/svelte-kit/src/error.html index 57772241..99279e5d 100644 --- a/frontend/svelte-kit/src/error.html +++ b/frontend/svelte-kit/src/error.html @@ -3,11 +3,67 @@ %sveltekit.error.message% + - -

My custom error page

-

Status: %sveltekit.status%

-

Message: %sveltekit.error.message%

+
+

%sveltekit.status%

+

%sveltekit.error.message%

+ Go to homepage +
diff --git a/frontend/svelte-kit/src/lib/models/api.d.ts b/frontend/svelte-kit/src/lib/models/api.d.ts new file mode 100644 index 00000000..4d81c08a --- /dev/null +++ b/frontend/svelte-kit/src/lib/models/api.d.ts @@ -0,0 +1,1019 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/admin/users/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["findById"]; + put: operations["update"]; + post?: never; + delete: operations["delete"]; + options?: never; + head?: never; + patch: operations["patch"]; + trace?: never; + }; + "/users/username/availability": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["usernameAvailability"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/email/availability": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["emailAvailability"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/verify-email": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["verifyEmail"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/verification-email": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["sendVerificationMail"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/tokens": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["authenticate"]; + delete: operations["deleteTokens"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/tokens/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["refreshTokens"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/register": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["register"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/password/reset": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["resetPassword"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/password/forgot": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["forgotPassword"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["findAll"]; + put?: never; + post: operations["create"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/profile": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["find"]; + put?: never; + post?: never; + delete: operations["delete_1"]; + options?: never; + head?: never; + patch: operations["patch_1"]; + trace?: never; + }; + "/profile/password": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch: operations["changePassword"]; + trace?: never; + }; + "/users": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["findAll_1"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["findById_1"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/users/username/{username}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["findByUsername"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/roles": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["findAll_2"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/tokens/devices": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["findAllDevices"]; + put?: never; + post?: never; + delete: operations["deleteTokensOnAllDevices"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/tokens/devices/{deviceId}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["revokeDevice"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + UserRequest: { + username: string; + /** Format: email */ + email: string; + password: string; + confirmPassword: string; + active: boolean; + lock: boolean; + roleNames: ("USER" | "ADMIN")[]; + }; + RoleDTO: { + name?: string; + }; + UserDTO: { + /** Format: int64 */ + id?: number; + username?: string; + email?: string; + active?: boolean; + lock?: boolean; + /** Format: date-time */ + createdAt?: string; + roles?: components["schemas"]["RoleDTO"][]; + }; + UsernameAvailabilityRequest: { + username: string; + }; + AvailabilityDTO: { + available?: boolean; + }; + EmailAvailabilityRequest: { + /** Format: email */ + email: string; + }; + VerifyEmailRequest: { + token: string; + }; + VerificationEmailRequest: { + usernameOrEmail: string; + }; + AuthTokensRequest: { + usernameOrEmail: string; + password: string; + }; + RegisterUserRequest: { + username: string; + /** Format: email */ + email: string; + password: string; + confirmPassword: string; + }; + ResetPasswordRequest: { + token: string; + password: string; + confirmPassword: string; + }; + ForgotPasswordRequest: { + /** Format: email */ + email: string; + }; + PatchProfileRequest: { + username?: string; + /** Format: email */ + email?: string; + }; + ChangePasswordRequest: { + currentPassword: string; + newPassword: string; + confirmNewPassword: string; + }; + PatchUserRequest: { + username?: string; + /** Format: email */ + email?: string; + password?: string; + confirmPassword?: string; + active?: boolean; + lock?: boolean; + roleNames?: ("USER" | "ADMIN")[]; + }; + Pageable: { + /** Format: int32 */ + page?: number; + /** Format: int32 */ + size?: number; + sort?: string[]; + }; + PageableDTOUserDTO: { + data?: components["schemas"]["UserDTO"][]; + /** Format: int64 */ + total?: number; + }; + DeviceDTO: { + deviceId?: string; + userAgent?: string; + /** Format: date-time */ + createdAt?: string; + /** Format: date-time */ + lastActiveAt?: string; + current?: boolean; + }; + ErrorMessage: { + /** Format: date-time */ + timestamp?: string; + /** Format: int32 */ + status?: number; + error?: string; + codes?: string[]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + findById: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["UserDTO"]; + }; + }; + }; + }; + update: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UserRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["UserDTO"]; + }; + }; + }; + }; + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + patch: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PatchUserRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["UserDTO"]; + }; + }; + }; + }; + usernameAvailability: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UsernameAvailabilityRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["AvailabilityDTO"]; + }; + }; + }; + }; + emailAvailability: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EmailAvailabilityRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["AvailabilityDTO"]; + }; + }; + }; + }; + verifyEmail: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["VerifyEmailRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + sendVerificationMail: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["VerificationEmailRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + authenticate: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AuthTokensRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + deleteTokens: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + refreshTokens: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + register: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RegisterUserRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["UserDTO"]; + }; + }; + }; + }; + resetPassword: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ResetPasswordRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + forgotPassword: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ForgotPasswordRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + findAll: { + parameters: { + query: { + pageable: components["schemas"]["Pageable"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["PageableDTOUserDTO"]; + }; + }; + }; + }; + create: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UserRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["UserDTO"]; + }; + }; + }; + }; + find: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["UserDTO"]; + }; + }; + }; + }; + delete_1: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + patch_1: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PatchProfileRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["UserDTO"]; + }; + }; + }; + }; + changePassword: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ChangePasswordRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + findAll_1: { + parameters: { + query: { + pageable: components["schemas"]["Pageable"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["PageableDTOUserDTO"]; + }; + }; + }; + }; + findById_1: { + parameters: { + query?: never; + header?: never; + path: { + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["UserDTO"]; + }; + }; + }; + }; + findByUsername: { + parameters: { + query?: never; + header?: never; + path: { + username: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["UserDTO"]; + }; + }; + }; + }; + findAll_2: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["RoleDTO"][]; + }; + }; + }; + }; + findAllDevices: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["DeviceDTO"][]; + }; + }; + }; + }; + deleteTokensOnAllDevices: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + revokeDevice: { + parameters: { + query?: never; + header?: never; + path: { + deviceId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; +} diff --git a/frontend/svelte-kit/src/lib/models/auth/device.ts b/frontend/svelte-kit/src/lib/models/auth/device.ts index 34f446cf..65703acb 100644 --- a/frontend/svelte-kit/src/lib/models/auth/device.ts +++ b/frontend/svelte-kit/src/lib/models/auth/device.ts @@ -1,7 +1,3 @@ -export interface Device { - deviceId: string; - userAgent: string | null; - createdAt: string; - lastActiveAt: string; - current: boolean; -} +import type { components } from '../api'; + +export type Device = Required; diff --git a/frontend/svelte-kit/src/lib/models/auth/jwt-payload.ts b/frontend/svelte-kit/src/lib/models/auth/jwt-payload.ts index 26531631..5185ef92 100644 --- a/frontend/svelte-kit/src/lib/models/auth/jwt-payload.ts +++ b/frontend/svelte-kit/src/lib/models/auth/jwt-payload.ts @@ -1,6 +1,7 @@ import type { RoleName } from '../user/role'; export interface JwtPayload { + jti: string; jwt: string; iss: string; iat: number; diff --git a/frontend/svelte-kit/src/lib/models/shared/availability.ts b/frontend/svelte-kit/src/lib/models/shared/availability.ts index da17cb97..2a117f63 100644 --- a/frontend/svelte-kit/src/lib/models/shared/availability.ts +++ b/frontend/svelte-kit/src/lib/models/shared/availability.ts @@ -1,3 +1,3 @@ -export interface Availability { - available: boolean; -} +import type { components } from '../api'; + +export type Availability = components['schemas']['AvailabilityDTO']; diff --git a/frontend/svelte-kit/src/lib/models/shared/error-message.ts b/frontend/svelte-kit/src/lib/models/shared/error-message.ts index d4739e2d..9b3d0ad9 100644 --- a/frontend/svelte-kit/src/lib/models/shared/error-message.ts +++ b/frontend/svelte-kit/src/lib/models/shared/error-message.ts @@ -1,10 +1,8 @@ -export interface ErrorMessage { - timestamp: Date; - status: number; - error: string; - codes: ErrorCode[]; -} +import type { components } from '../api'; + +export type ErrorMessage = Required; +// ErrorCode is frontend-specific — maps backend error codes to i18n message keys export enum ErrorCode { API_ERROR_AUTH_UNAUTHORIZED = 'API_ERROR_AUTH_UNAUTHORIZED', API_ERROR_AUTH_FORBIDDEN = 'API_ERROR_AUTH_FORBIDDEN', diff --git a/frontend/svelte-kit/src/lib/models/user/role.ts b/frontend/svelte-kit/src/lib/models/user/role.ts index f13d9ee1..ce2bec8b 100644 --- a/frontend/svelte-kit/src/lib/models/user/role.ts +++ b/frontend/svelte-kit/src/lib/models/user/role.ts @@ -1,7 +1,8 @@ -export interface Role { - name: RoleName; -} +import type { components } from '../api'; + +export type Role = components['schemas']['RoleDTO']; +// Kept as enum for runtime usage (RoleName.ADMIN, RoleName.USER) export enum RoleName { USER = 'USER', ADMIN = 'ADMIN', diff --git a/frontend/svelte-kit/src/lib/models/user/user.ts b/frontend/svelte-kit/src/lib/models/user/user.ts index d837e567..1bc2ab48 100644 --- a/frontend/svelte-kit/src/lib/models/user/user.ts +++ b/frontend/svelte-kit/src/lib/models/user/user.ts @@ -1,24 +1,14 @@ +import type { components } from '../api'; import type { Role } from './role'; -export interface AdminUser { - id: number; - username: string; - email: string; +type UserDTO = Required; + +export type AdminUser = Omit & { active: boolean; lock: boolean; - createdAt: Date; roles: Role[]; -} +}; -export interface SimplifiedUser { - id: number; - username: string; - createdAt: Date; -} +export type Profile = Pick; -export interface Profile { - id: number; - username: string; - email: string; - createdAt: Date; -} +export type SimplifiedUser = Pick; diff --git a/frontend/svelte-kit/src/lib/server/apis/api.ts b/frontend/svelte-kit/src/lib/server/apis/api.ts index f0394810..37a852dd 100644 --- a/frontend/svelte-kit/src/lib/server/apis/api.ts +++ b/frontend/svelte-kit/src/lib/server/apis/api.ts @@ -53,7 +53,8 @@ export function apiErrors( form: SuperValidated>, ) { for (const code of errorMessage.codes) { - const message = ErrorCode[code] ? m[ErrorCode[code]]() : m.API_ERROR_UNKNOWN(); + const key = ErrorCode[code as keyof typeof ErrorCode]; + const message = key ? m[key]() : m.API_ERROR_UNKNOWN(); setError(form, message); } return fail(errorMessage.status, { form }); diff --git a/frontend/svelte-kit/src/routes/+layout.server.ts b/frontend/svelte-kit/src/routes/+layout.server.ts index 91d0181c..83a71bf5 100644 --- a/frontend/svelte-kit/src/routes/+layout.server.ts +++ b/frontend/svelte-kit/src/routes/+layout.server.ts @@ -18,8 +18,10 @@ export const load = (async ({ locals, cookies, url }) => { ); if ('error' in response) { - if (response.status == 401) + if (response.status === 401) { cookies.delete('accessToken', { path: '/', domain: env.DOMAIN_NAME }); + redirect(302, url.pathname + url.search); + } error(response.status, { message: response.error }); }