Skip to content

fix: prepends /v1 to all user/template urls and added proxies#2659

Merged
stalniy merged 2 commits intomainfrom
fix/user-template-urls
Feb 3, 2026
Merged

fix: prepends /v1 to all user/template urls and added proxies#2659
stalniy merged 2 commits intomainfrom
fix/user-template-urls

Conversation

@stalniy
Copy link
Contributor

@stalniy stalniy commented Feb 3, 2026

Why

unification and small payload fix

Summary by CodeRabbit

  • New Features

    • User settings accept optional/nullable bio and social usernames; newsletter subscription uses the authenticated user's id.
  • Refactor

    • API and frontend endpoints migrated to versioned /v1/ paths; legacy routes now proxy or redirect to v1 to preserve existing behavior.
  • Tests

    • Updated tests and assertions to expect the new versioned endpoints.

@stalniy stalniy requested a review from a team as a code owner February 3, 2026 11:29
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds an internal proxy handler to the API legacy router and migrates legacy /user/... routes and frontend calls to /v1/user/.... Adjusts controller constructor and updateSettings input types, updates auth field usage (currentUser.id), and updates tests to expect versioned endpoints.

Changes

Cohort / File(s) Summary
API Proxy Infrastructure
apps/api/src/routers/legacyRouter.ts
Adds an internal proxyToV1 function and local proxy docs; replaces legacy pricing route with proxied handling and adds multiple /user/... legacy routes that either 301 → /v1/... or are proxied to /v1/....
Backend Routing
apps/api/src/user/routes/user-settings/user-settings.router.ts, apps/api/src/user/routes/user-templates/user-templates.router.ts
All route paths updated from /user/... to /v1/user/.... updateSettings request schema made more permissive (optional/nullable fields).
Backend Controller
apps/api/src/user/controllers/user/user.controller.ts
Removed injected UserRepository parameter from UserController constructor; updateSettings parameter fields are now optional/nullable; authentication usage switched from currentUser.userId to currentUser.id.
Frontend — Components
apps/deploy-web/src/components/sdl/EditDescriptionForm.tsx, apps/deploy-web/src/components/sdl/SimpleSdlBuilderForm.tsx, apps/deploy-web/src/components/user/UserSettingsForm.tsx
Updated API endpoint strings to use /v1/user/... for template and settings endpoints.
Frontend — Hooks & Tests
apps/deploy-web/src/queries/useSaveSettings.ts, apps/deploy-web/src/queries/useTemplateQuery.tsx, apps/deploy-web/src/queries/useSaveSettings.spec.tsx, apps/deploy-web/src/queries/useTemplateQuery.spec.tsx
Updated hooks and tests to call /v1/user/... endpoints and adjusted test expectations to versioned URLs; payloads and behaviors unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client as Client (frontend)
  participant Legacy as Legacy API\n(legacyRouter)
  participant V1 as V1 API\n(internal /v1 handlers)
  participant DB as Database
  Client->>Legacy: HTTP request to /user/... or /pricing
  alt route configured as 301
    Legacy-->>Client: 301 redirect to /v1/...
  else proxied route
    Legacy->>V1: HTTP forward to http://127.0.0.1:<PORT>/v1/... (preserve path/query/body)
    V1->>DB: query/update
    DB-->>V1: result
    V1-->>Legacy: response
    Legacy-->>Client: proxied response
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ygrishajev

Poem

🐇 I hopped from old routes to v1's bright trail,

I nudged requests forward, left no loose mail,
I proxy and redirect with a whiskered cheer,
Paths now versioned — the future is near,
Tiny paws, big changes — hop on, let's sail!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: prepending /v1 to user/template URLs and adding proxy handlers in the legacy router.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/user-template-urls

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Feb 3, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
2188 1 2187 1
View the top 1 failed test(s) by shortest run time
API Docs GET /v1/doc returns docs with all routes expected
Stack Traces | 0.173s run time
Error: expect(received).toMatchSnapshot()

Snapshot name: `API Docs GET /v1/doc returns docs with all routes expected 1`

- Snapshot  - 493
+ Received  + 492

@@ -1515,503 +1515,10 @@
          "tags": [
            "Leases",
          ],
        },
      },
-     "/user/addFavoriteTemplate/{templateId}": {
-       "post": {
-         "parameters": [
-           {
-             "in": "path",
-             "name": "templateId",
-             "required": true,
-             "schema": {
-               "format": "uuid",
-               "type": "string",
-             },
-           },
-         ],
-         "responses": {
-           "200": {
-             "description": "Template added to favorites",
-           },
-           "400": {
-             "description": "Invalid template ID",
-           },
-           "401": {
-             "description": "Unauthorized",
-           },
-         },
-         "security": [
-           {
-             "BearerAuth": [],
-           },
-           {
-             "ApiKeyAuth": [],
-           },
-         ],
-         "summary": "Add favorite template",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/byUsername/{username}": {
-       "get": {
-         "parameters": [
-           {
-             "in": "path",
-             "name": "username",
-             "required": true,
-             "schema": {
-               "type": "string",
-             },
-           },
-         ],
-         "responses": {
-           "200": {
-             "content": {
-               "application/json": {
-                 "schema": {
-                   "properties": {
-                     "bio": {
-                       "nullable": true,
-                       "type": "string",
-                     },
-                     "username": {
-                       "type": "string",
-                     },
-                   },
-                   "required": [
-                     "username",
-                     "bio",
-                   ],
-                   "type": "object",
-                 },
-               },
-             },
-             "description": "Returns user profile",
-           },
-           "404": {
-             "description": "User not found",
-           },
-         },
-         "security": [],
-         "summary": "Get user by username",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/checkUsernameAvailability/{username}": {
-       "get": {
-         "parameters": [
-           {
-             "in": "path",
-             "name": "username",
-             "required": true,
-             "schema": {
-               "type": "string",
-             },
-           },
-         ],
-         "responses": {
-           "200": {
-             "content": {
-               "application/json": {
-                 "schema": {
-                   "properties": {
-                     "isAvailable": {
-                       "type": "boolean",
-                     },
-                   },
-                   "required": [
-                     "isAvailable",
-                   ],
-                   "type": "object",
-                 },
-               },
-             },
-             "description": "Returns username availability",
-           },
-         },
-         "security": [],
-         "summary": "Check if username is available",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/deleteTemplate/{id}": {
-       "delete": {
-         "parameters": [
-           {
-             "in": "path",
-             "name": "id",
-             "required": true,
-             "schema": {
-               "format": "uuid",
-               "type": "string",
-             },
-           },
-         ],
-         "responses": {
-           "200": {
-             "description": "Template deleted successfully",
-           },
-           "400": {
-             "description": "Invalid template ID",
-           },
-           "401": {
-             "description": "Unauthorized",
-           },
-         },
-         "security": [
-           {
-             "BearerAuth": [],
-           },
-           {
-             "ApiKeyAuth": [],
-           },
-         ],
-         "summary": "Delete template",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/favoriteTemplates": {
-       "get": {
-         "responses": {
-           "200": {
-             "description": "Returns favorite templates",
-           },
-           "401": {
-             "description": "Unauthorized",
-           },
-         },
-         "security": [
-           {
-             "BearerAuth": [],
-           },
-           {
-             "ApiKeyAuth": [],
-           },
-         ],
-         "summary": "Get favorite templates",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/removeFavoriteTemplate/{templateId}": {
-       "delete": {
-         "parameters": [
-           {
-             "in": "path",
-             "name": "templateId",
-             "required": true,
-             "schema": {
-               "format": "uuid",
-               "type": "string",
-             },
-           },
-         ],
-         "responses": {
-           "200": {
-             "description": "Template removed from favorites",
-           },
-           "400": {
-             "description": "Invalid template ID",
-           },
-           "401": {
-             "description": "Unauthorized",
-           },
-         },
-         "security": [
-           {
-             "BearerAuth": [],
-           },
-           {
-             "ApiKeyAuth": [],
-           },
-         ],
-         "summary": "Remove favorite template",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/saveTemplate": {
-       "post": {
-         "requestBody": {
-           "content": {
-             "application/json": {
-               "schema": {
-                 "properties": {
-                   "cpu": {
-                     "type": "number",
-                   },
-                   "description": {
-                     "maxLength": 2000,
-                     "type": "string",
-                   },
-                   "id": {
-                     "format": "uuid",
-                     "type": "string",
-                   },
-                   "isPublic": {
-                     "type": "boolean",
-                   },
-                   "ram": {
-                     "type": "number",
-                   },
-                   "sdl": {
-                     "type": "string",
-                   },
-                   "storage": {
-                     "type": "number",
-                   },
-                   "title": {
-                     "type": "string",
-                   },
-                 },
-                 "required": [
-                   "sdl",
-                   "title",
-                   "cpu",
-                   "ram",
-                   "storage",
-                   "isPublic",
-                 ],
-                 "type": "object",
-               },
-             },
-           },
-         },
-         "responses": {
-           "200": {
-             "description": "Template saved successfully",
-           },
-           "400": {
-             "description": "Invalid input",
-           },
-           "401": {
-             "description": "Unauthorized",
-           },
-         },
-         "security": [
-           {
-             "BearerAuth": [],
-           },
-           {
-             "ApiKeyAuth": [],
-           },
-         ],
-         "summary": "Save template",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/saveTemplateDesc": {
-       "post": {
-         "requestBody": {
-           "content": {
-             "application/json": {
-               "schema": {
-                 "properties": {
-                   "description": {
-                     "type": "string",
-                   },
-                   "id": {
-                     "format": "uuid",
-                     "type": "string",
-                   },
-                 },
-                 "required": [
-                   "id",
-                   "description",
-                 ],
-                 "type": "object",
-               },
-             },
-           },
-         },
-         "responses": {
-           "200": {
-             "description": "Template description saved successfully",
-           },
-           "401": {
-             "description": "Unauthorized",
-           },
-         },
-         "security": [
-           {
-             "BearerAuth": [],
-           },
-           {
-             "ApiKeyAuth": [],
-           },
-         ],
-         "summary": "Save template description",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/subscribeToNewsletter": {
-       "post": {
-         "responses": {
-           "200": {
-             "description": "Subscribed successfully",
-           },
-           "401": {
-             "description": "Unauthorized",
-           },
-         },
-         "security": [
-           {
-             "BearerAuth": [],
-           },
-           {
-             "ApiKeyAuth": [],
-           },
-         ],
-         "summary": "Subscribe to newsletter",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/template/{id}": {
-       "get": {
-         "parameters": [
-           {
-             "in": "path",
-             "name": "id",
-             "required": true,
-             "schema": {
-               "format": "uuid",
-               "type": "string",
-             },
-           },
-         ],
-         "responses": {
-           "200": {
-             "description": "Returns template",
-           },
-           "400": {
-             "description": "Invalid template ID",
-           },
-           "404": {
-             "description": "Template not found",
-           },
-         },
-         "security": [],
-         "summary": "Get template by ID",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/templates/{username}": {
-       "get": {
-         "parameters": [
-           {
-             "in": "path",
-             "name": "username",
-             "required": true,
-             "schema": {
-               "type": "string",
-             },
-           },
-         ],
-         "responses": {
-           "200": {
-             "description": "Returns templates",
-           },
-           "404": {
-             "description": "User not found",
-           },
-         },
-         "security": [],
-         "summary": "Get templates by username",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
-     "/user/updateSettings": {
-       "put": {
-         "requestBody": {
-           "content": {
-             "application/json": {
-               "schema": {
-                 "properties": {
-                   "bio": {
-                     "maxLength": 5000,
-                     "type": "string",
-                   },
-                   "githubUsername": {
-                     "maxLength": 200,
-                     "type": "string",
-                   },
-                   "subscribedToNewsletter": {
-                     "type": "boolean",
-                   },
-                   "twitterUsername": {
-                     "maxLength": 200,
-                     "type": "string",
-                   },
-                   "username": {
-                     "pattern": "^[a-zA-Z0-9_-]+$",
-                     "type": "string",
-                   },
-                   "youtubeUsername": {
-                     "maxLength": 200,
-                     "type": "string",
-                   },
-                 },
-                 "required": [
-                   "username",
-                   "subscribedToNewsletter",
-                   "bio",
-                   "youtubeUsername",
-                   "twitterUsername",
-                   "githubUsername",
-                 ],
-                 "type": "object",
-               },
-             },
-           },
-         },
-         "responses": {
-           "200": {
-             "description": "Settings updated successfully",
-           },
-           "400": {
-             "description": "Invalid input",
-           },
-           "401": {
-             "description": "Unauthorized",
-           },
-         },
-         "security": [
-           {
-             "BearerAuth": [],
-           },
-           {
-             "ApiKeyAuth": [],
-           },
-         ],
-         "summary": "Update user settings",
-         "tags": [
-           "Users",
-         ],
-       },
-     },
      "/v1/addresses/{address}": {
        "get": {
          "parameters": [
            {
              "in": "path",
@@ -15072,10 +14579,196 @@
          "tags": [
            "Billing",
          ],
        },
      },
+     ".../v1/user/addFavoriteTemplate/{templateId}": {
+       "post": {
+         "parameters": [
+           {
+             "in": "path",
+             "name": "templateId",
+             "required": true,
+             "schema": {
+               "format": "uuid",
+               "type": "string",
+             },
+           },
+         ],
+         "responses": {
+           "200": {
+             "description": "Template added to favorites",
+           },
+           "400": {
+             "description": "Invalid template ID",
+           },
+           "401": {
+             "description": "Unauthorized",
+           },
+         },
+         "security": [
+           {
+             "BearerAuth": [],
+           },
+           {
+             "ApiKeyAuth": [],
+           },
+         ],
+         "summary": "Add favorite template",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     ".../v1/user/byUsername/{username}": {
+       "get": {
+         "parameters": [
+           {
+             "in": "path",
+             "name": "username",
+             "required": true,
+             "schema": {
+               "type": "string",
+             },
+           },
+         ],
+         "responses": {
+           "200": {
+             "content": {
+               "application/json": {
+                 "schema": {
+                   "properties": {
+                     "bio": {
+                       "nullable": true,
+                       "type": "string",
+                     },
+                     "username": {
+                       "type": "string",
+                     },
+                   },
+                   "required": [
+                     "username",
+                     "bio",
+                   ],
+                   "type": "object",
+                 },
+               },
+             },
+             "description": "Returns user profile",
+           },
+           "404": {
+             "description": "User not found",
+           },
+         },
+         "security": [],
+         "summary": "Get user by username",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     ".../v1/user/checkUsernameAvailability/{username}": {
+       "get": {
+         "parameters": [
+           {
+             "in": "path",
+             "name": "username",
+             "required": true,
+             "schema": {
+               "type": "string",
+             },
+           },
+         ],
+         "responses": {
+           "200": {
+             "content": {
+               "application/json": {
+                 "schema": {
+                   "properties": {
+                     "isAvailable": {
+                       "type": "boolean",
+                     },
+                   },
+                   "required": [
+                     "isAvailable",
+                   ],
+                   "type": "object",
+                 },
+               },
+             },
+             "description": "Returns username availability",
+           },
+         },
+         "security": [],
+         "summary": "Check if username is available",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     ".../v1/user/deleteTemplate/{id}": {
+       "delete": {
+         "parameters": [
+           {
+             "in": "path",
+             "name": "id",
+             "required": true,
+             "schema": {
+               "format": "uuid",
+               "type": "string",
+             },
+           },
+         ],
+         "responses": {
+           "200": {
+             "description": "Template deleted successfully",
+           },
+           "400": {
+             "description": "Invalid template ID",
+           },
+           "401": {
+             "description": "Unauthorized",
+           },
+         },
+         "security": [
+           {
+             "BearerAuth": [],
+           },
+           {
+             "ApiKeyAuth": [],
+           },
+         ],
+         "summary": "Delete template",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     ".../v1/user/favoriteTemplates": {
+       "get": {
+         "responses": {
+           "200": {
+             "description": "Returns favorite templates",
+           },
+           "401": {
+             "description": "Unauthorized",
+           },
+         },
+         "security": [
+           {
+             "BearerAuth": [],
+           },
+           {
+             "ApiKeyAuth": [],
+           },
+         ],
+         "summary": "Get favorite templates",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
      ".../v1/user/me": {
        "get": {
          "responses": {
            "200": {
              "content": {
@@ -15151,10 +14844,316 @@
            {
              "ApiKeyAuth": [],
            },
          ],
          "summary": "Retrieves the logged in user",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     ".../v1/user/removeFavoriteTemplate/{templateId}": {
+       "delete": {
+         "parameters": [
+           {
+             "in": "path",
+             "name": "templateId",
+             "required": true,
+             "schema": {
+               "format": "uuid",
+               "type": "string",
+             },
+           },
+         ],
+         "responses": {
+           "200": {
+             "description": "Template removed from favorites",
+           },
+           "400": {
+             "description": "Invalid template ID",
+           },
+           "401": {
+             "description": "Unauthorized",
+           },
+         },
+         "security": [
+           {
+             "BearerAuth": [],
+           },
+           {
+             "ApiKeyAuth": [],
+           },
+         ],
+         "summary": "Remove favorite template",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     ".../v1/user/saveTemplate": {
+       "post": {
+         "requestBody": {
+           "content": {
+             "application/json": {
+               "schema": {
+                 "properties": {
+                   "cpu": {
+                     "type": "number",
+                   },
+                   "description": {
+                     "maxLength": 2000,
+                     "type": "string",
+                   },
+                   "id": {
+                     "format": "uuid",
+                     "type": "string",
+                   },
+                   "isPublic": {
+                     "type": "boolean",
+                   },
+                   "ram": {
+                     "type": "number",
+                   },
+                   "sdl": {
+                     "type": "string",
+                   },
+                   "storage": {
+                     "type": "number",
+                   },
+                   "title": {
+                     "type": "string",
+                   },
+                 },
+                 "required": [
+                   "sdl",
+                   "title",
+                   "cpu",
+                   "ram",
+                   "storage",
+                   "isPublic",
+                 ],
+                 "type": "object",
+               },
+             },
+           },
+         },
+         "responses": {
+           "200": {
+             "description": "Template saved successfully",
+           },
+           "400": {
+             "description": "Invalid input",
+           },
+           "401": {
+             "description": "Unauthorized",
+           },
+         },
+         "security": [
+           {
+             "BearerAuth": [],
+           },
+           {
+             "ApiKeyAuth": [],
+           },
+         ],
+         "summary": "Save template",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     "....../v1/user/saveTemplateDesc": {
+       "post": {
+         "requestBody": {
+           "content": {
+             "application/json": {
+               "schema": {
+                 "properties": {
+                   "description": {
+                     "type": "string",
+                   },
+                   "id": {
+                     "format": "uuid",
+                     "type": "string",
+                   },
+                 },
+                 "required": [
+                   "id",
+                   "description",
+                 ],
+                 "type": "object",
+               },
+             },
+           },
+         },
+         "responses": {
+           "200": {
+             "description": "Template description saved successfully",
+           },
+           "401": {
+             "description": "Unauthorized",
+           },
+         },
+         "security": [
+           {
+             "BearerAuth": [],
+           },
+           {
+             "ApiKeyAuth": [],
+           },
+         ],
+         "summary": "Save template description",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     ".../v1/user/subscribeToNewsletter": {
+       "post": {
+         "responses": {
+           "200": {
+             "description": "Subscribed successfully",
+           },
+           "401": {
+             "description": "Unauthorized",
+           },
+         },
+         "security": [
+           {
+             "BearerAuth": [],
+           },
+           {
+             "ApiKeyAuth": [],
+           },
+         ],
+         "summary": "Subscribe to newsletter",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     ".../v1/user/template/{id}": {
+       "get": {
+         "parameters": [
+           {
+             "in": "path",
+             "name": "id",
+             "required": true,
+             "schema": {
+               "format": "uuid",
+               "type": "string",
+             },
+           },
+         ],
+         "responses": {
+           "200": {
+             "description": "Returns template",
+           },
+           "400": {
+             "description": "Invalid template ID",
+           },
+           "404": {
+             "description": "Template not found",
+           },
+         },
+         "security": [],
+         "summary": "Get template by ID",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     "....../v1/user/templates/{username}": {
+       "get": {
+         "parameters": [
+           {
+             "in": "path",
+             "name": "username",
+             "required": true,
+             "schema": {
+               "type": "string",
+             },
+           },
+         ],
+         "responses": {
+           "200": {
+             "description": "Returns templates",
+           },
+           "404": {
+             "description": "User not found",
+           },
+         },
+         "security": [],
+         "summary": "Get templates by username",
+         "tags": [
+           "Users",
+         ],
+       },
+     },
+     ".../v1/user/updateSettings": {
+       "put": {
+         "requestBody": {
+           "content": {
+             "application/json": {
+               "schema": {
+                 "properties": {
+                   "bio": {
+                     "maxLength": 5000,
+                     "nullable": true,
+                     "type": "string",
+                   },
+                   "githubUsername": {
+                     "maxLength": 200,
+                     "nullable": true,
+                     "type": "string",
+                   },
+                   "subscribedToNewsletter": {
+                     "type": "boolean",
+                   },
+                   "twitterUsername": {
+                     "maxLength": 200,
+                     "nullable": true,
+                     "type": "string",
+                   },
+                   "username": {
+                     "pattern": "^[a-zA-Z0-9_-]+$",
+                     "type": "string",
+                   },
+                   "youtubeUsername": {
+                     "maxLength": 200,
+                     "nullable": true,
+                     "type": "string",
+                   },
+                 },
+                 "required": [
+                   "username",
+                 ],
+                 "type": "object",
+               },
+             },
+           },
+         },
+         "responses": {
+           "200": {
+             "description": "Settings updated successfully",
+           },
+           "400": {
+             "description": "Invalid input",
+           },
+           "401": {
+             "description": "Unauthorized",
+           },
+         },
+         "security": [
+           {
+             "BearerAuth": [],
+           },
+           {
+             "ApiKeyAuth": [],
+           },
+         ],
+         "summary": "Update user settings",
          "tags": [
            "Users",
          ],
        },
      },
    at Object.<anonymous> (.../test/functional/docs.spec.ts:13:20)
    at processTicksAndRejections (node:internal/process/task_queues:103:5)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/deploy-web/src/components/sdl/EditDescriptionForm.tsx (1)

39-51: ⚠️ Potential issue | 🟡 Minor

Missing error handling could leave the form in a broken state.

If the API call fails, isSaving remains true indefinitely, preventing further user interaction. Consider adding error handling similar to other forms in this PR.

🛡️ Proposed fix
   const onSubmit = async (data: FormValues) => {
     setIsSaving(true);
-    await consoleApiHttpClient.post("/v1/user/saveTemplateDesc", {
-      id: id,
-      description: data.description
-    });
-
-    enqueueSnackbar(<Snackbar title="Description saved!" iconVariant="success" />, {
-      variant: "success"
-    });
-
-    onSave(data.description);
+    try {
+      await consoleApiHttpClient.post("/v1/user/saveTemplateDesc", {
+        id: id,
+        description: data.description
+      });
+
+      enqueueSnackbar(<Snackbar title="Description saved!" iconVariant="success" />, {
+        variant: "success"
+      });
+
+      onSave(data.description);
+    } catch {
+      enqueueSnackbar(<Snackbar title="Error saving description" iconVariant="error" />, {
+        variant: "error"
+      });
+    } finally {
+      setIsSaving(false);
+    }
   };
apps/deploy-web/src/components/user/UserSettingsForm.tsx (1)

72-86: ⚠️ Potential issue | 🟡 Minor

Missing error handling for username availability check.

If the API call fails, isCheckingAvailability remains true, showing a perpetual spinner. Consider adding error handling.

🛡️ Proposed fix
       const timeoutId = setTimeout(async () => {
         setIsCheckingAvailability(true);
-        const response = await consoleApiHttpClient.get(`/v1/user/checkUsernameAvailability/${username}`);
-
-        setIsCheckingAvailability(false);
-        setIsAvailable(response.data.isAvailable);
+        try {
+          const response = await consoleApiHttpClient.get(`/v1/user/checkUsernameAvailability/${username}`);
+          setIsAvailable(response.data.isAvailable);
+        } catch {
+          setIsAvailable(null);
+        } finally {
+          setIsCheckingAvailability(false);
+        }
       }, 500);
apps/api/src/user/routes/user-templates/user-templates.router.ts (1)

84-116: ⚠️ Potential issue | 🟠 Major

OpenAPI response codes don’t match 204 handlers.

These routes return 204 but declare 200 in the OpenAPI responses, which can break client generation and contract validation. Align the response codes (or change handlers to return 200 with a body).

✅ Suggested fix (align OpenAPI responses to 204)
   responses: {
-    200: {
+    204: {
       description: "Template description saved successfully"
     },
       description: "Unauthorized"
     }
   }

   responses: {
-    200: {
+    204: {
       description: "Template deleted successfully"
     },
       description: "Invalid template ID"
     },
       description: "Unauthorized"
     }
   }

   responses: {
-    200: {
+    204: {
       description: "Template added to favorites"
     },
       description: "Invalid template ID"
     },
       description: "Unauthorized"
     }
   }

   responses: {
-    200: {
+    204: {
       description: "Template removed from favorites"
     },
       description: "Invalid template ID"
     },
       description: "Unauthorized"
     }
   }

Also applies to: 145-173, 196-224, 226-254

🤖 Fix all issues with AI agents
In `@apps/api/src/user/controllers/user/user.controller.ts`:
- Around line 57-69: The assertion in updateSettings checks
this.authService.currentUser?.userId but the code uses
this.authService.currentUser.id; update the assert to check the correct property
(this.authService.currentUser?.id) or make both use the same property name so
they match, ensuring the assert refers to the actual identifier used by
authService.currentUser before calling this.userService.updateUserDetails.
- Around line 77-81: In subscribeToNewsletter the assertion checks
authService.currentUser?.userId but the code then reads
authService.currentUser.id, causing an inconsistency; update the assertion and
variable usage to the same property (prefer authService.currentUser?.id) or
rename the extracted variable to match the asserted property so the assert and
the userId assignment both reference the same field (refer to
subscribeToNewsletter and authService.currentUser).

In `@apps/api/src/user/routes/user-settings/user-settings.router.ts`:
- Around line 56-63: The backend marks bio, youtubeUsername, twitterUsername and
githubUsername as nullable but the frontend currently makes them optional;
update the frontend UserSettings type and the form Zod schema so those four
fields are required and accept null (e.g. in the UserSettings interface change
the properties to string | null, and in the UserSettingsForm.tsx schema change
bio, youtubeUsername, twitterUsername, githubUsername to
z.string().max(...).nullable()); also ensure the form default values populate
these fields (explicitly null when empty) so getValues()/submission matches the
backend nullable contract.
🧹 Nitpick comments (4)
apps/api/src/routers/legacyRouter.ts (3)

201-227: Inconsistent redirect status codes.

The new user routes use hardcoded 301 (Permanent Redirect) while the rest of the file uses the redirectStatusCode constant set to 302 (Temporary Redirect) on line 6.

If 301 is intentional for these user routes (since /v1/ paths are now canonical), consider either:

  1. Updating redirectStatusCode to 301 if all legacy routes should use permanent redirects, or
  2. Creating a separate constant like permanentRedirectStatusCode = 301 to make the intent explicit.
♻️ Proposed refactor using a named constant
 const redirectStatusCode = 302; // Temporary Redirect
+const permanentRedirectStatusCode = 301; // Permanent Redirect

Then replace all hardcoded 301 values with permanentRedirectStatusCode.


237-238: Comment mentions hardcoded port but code uses environment variable.

The comment says "http://127.0.0.1:3000/v1/*" but the actual implementation uses process.env.PORT || 3000. Update the comment to reflect the dynamic port behavior.

📝 Proposed comment fix
-/**
- * Local http proxy to http://127.0.0.1:3000/v1/*
- */
+/**
+ * Local http proxy to http://127.0.0.1:${PORT}/v1/*
+ */

240-244: Consider adding error handling and timeout to the proxy function.

The fetch call has no error handling or timeout. If the internal v1 endpoint is slow or unavailable, requests could hang indefinitely or fail with unhandled errors.

♻️ Proposed improvement with timeout and error handling
 function proxyToV1(c: Context) {
   const originalUrl = new URL(c.req.url);
   const url = `http://127.0.0.1:${process.env.PORT || 3000}/v1${originalUrl.pathname}${originalUrl.search}`;
 
-  return fetch(new Request(url, c.req.raw));
+  return fetch(new Request(url, c.req.raw), { signal: AbortSignal.timeout(30000) })
+    .catch(() => c.json({ error: "Internal proxy error" }, 502));
 }
apps/deploy-web/src/queries/useTemplateQuery.spec.tsx (1)

90-90: Consider using useUserFavoriteTemplates.name for consistency.

As per coding guidelines, use <Subject>.name in the root describe suite description to enable automated refactoring tools. The same applies to useAddFavoriteTemplate (line 272) and useRemoveFavoriteTemplate (line 315).

♻️ Suggested fix
-  describe("useUserFavoriteTemplates", () => {
+  describe(useUserFavoriteTemplates.name, () => {

Similar changes for lines 272 and 315.

ygrishajev
ygrishajev previously approved these changes Feb 3, 2026
@stalniy stalniy force-pushed the fix/user-template-urls branch 2 times, most recently from a96dbb4 to c8d1dd0 Compare February 3, 2026 13:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/api/src/user/routes/user-settings/user-settings.router.ts (2)

81-85: ⚠️ Potential issue | 🟡 Minor

Response status code mismatch with OpenAPI spec.

The route handler returns c.body(null, 204) (No Content), but the OpenAPI responses definition declares 200 as the success response. This inconsistency may cause issues with API documentation and client code generation.

Update either the response definition to 204 or the handler to return 200:

🐛 Proposed fix to align OpenAPI spec with implementation
   responses: {
-    200: {
-      description: "Settings updated successfully"
+    204: {
+      description: "Settings updated successfully"
     },

134-137: ⚠️ Potential issue | 🟡 Minor

Same response status code mismatch in subscribeToNewsletter.

The handler returns 204 but the OpenAPI spec declares 200. Apply the same fix here:

🐛 Proposed fix
   responses: {
-    200: {
-      description: "Subscribed successfully"
+    204: {
+      description: "Subscribed successfully"
     },
🧹 Nitpick comments (1)
apps/deploy-web/src/queries/useTemplateQuery.spec.tsx (1)

90-90: Consider using function .name for describe block consistency.

Some describe blocks use hardcoded strings while others use .name references. For consistency and to support automated refactoring tools, consider updating these:

  • Line 90: "useUserFavoriteTemplates"useUserFavoriteTemplates.name
  • Line 272: "useAddFavoriteTemplate"useAddFavoriteTemplate.name
  • Line 315: "useRemoveFavoriteTemplate"useRemoveFavoriteTemplate.name
  • Line 358: "useTemplates"useTemplates.name

As per coding guidelines: Use <Subject>.name in the root describe suite description instead of hardcoded class/service name strings to enable automated refactoring tools to find all references.

Also applies to: 272-272, 315-315, 358-358

@stalniy stalniy force-pushed the fix/user-template-urls branch from c8d1dd0 to a939298 Compare February 3, 2026 13:37
@stalniy stalniy force-pushed the fix/user-template-urls branch from a939298 to 2e5fd76 Compare February 3, 2026 13:37
@stalniy stalniy merged commit 4834db0 into main Feb 3, 2026
7 checks passed
@stalniy stalniy deleted the fix/user-template-urls branch February 3, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments