Skip to content

feat: Add clone project template functionality - #367

Merged
yash-pouranik merged 10 commits into
mainfrom
feature/clone-template
Jul 27, 2026
Merged

feat: Add clone project template functionality#367
yash-pouranik merged 10 commits into
mainfrom
feature/clone-template

Conversation

@yash-pouranik

@yash-pouranik yash-pouranik commented Jul 27, 2026

Copy link
Copy Markdown
Member

Project Cloning feature
And Bring ur own GCS

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added configurable project templates with automatic collection/auth setup during project creation.
    • Added a “Clone Project” action and clone flow from the templates page.
    • Added Google Cloud Storage (GCS) support across the dashboard UI and storage handling.
  • Bug Fixes

    • Default project descriptions to an empty string when omitted.
    • Improved project-limit and transaction-related error handling.
  • Tests

    • Expanded Jest coverage for template-based project creation, including transaction behavior.
  • Documentation

    • Updated Google site verification metadata formatting.

@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ur-backend-web-dashboard Ready Ready Preview, Comment Jul 27, 2026 12:21pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
urbackend Skipped Skipped Jul 27, 2026 12:21pm

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Project templates can be cloned from the dashboard into configured projects. The backend applies template collections and authentication defaults, while common storage utilities and settings support Google Cloud Storage.

Changes

Project template cloning

Layer / File(s) Summary
Template contract and project creation
packages/common/src/utils/templates.js, packages/common/src/index.js, packages/common/src/utils/input.validation.js, apps/dashboard-api/src/controllers/project.controller.js
Defines and exports project templates, accepts templateId, applies valid template configuration, handles project limits and transaction fallbacks, and returns structured creation responses.
Dashboard template cloning flow
apps/web-dashboard/src/pages/Templates.jsx
Adds clone controls, a modal form, project creation submission, toast feedback, navigation, and loading-state handling.
Template creation validation
apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js
Verifies valid and invalid templates, authentication collections, validation, project limits, fallback behavior, and transaction lifecycle calls.

Google Cloud Storage support

Layer / File(s) Summary
GCS storage configuration
apps/web-dashboard/src/components/Settings/StorageConfigForm.jsx
Adds GCS selection, validation, endpoint defaults, HMAC labels, and GCS-specific public URL guidance.
GCS storage runtime
packages/common/src/utils/storage.manager.js
Adds GCS public URLs, endpoint handling, signed upload support, uploaded-file verification, and S3-compatible client configuration.

Documentation verification

Layer / File(s) Summary
SEO verification metadata
mintlify/docs/docs.json
Updates the Google site-verification fields and metadata shape.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Templates
  participant DashboardAPI
  participant MongoDB
  Templates->>DashboardAPI: Submit templateId, name, and description
  DashboardAPI->>DashboardAPI: Apply template collections and auth settings
  DashboardAPI->>MongoDB: Save the new Project
  MongoDB-->>DashboardAPI: Return created project
  DashboardAPI-->>Templates: Return HTTP 201 and project data
  Templates->>Templates: Navigate to the created project
Loading

Suggested labels: feature, backend, frontend, enhancement, type:feature, level:intermediate

Suggested reviewers: nitin-kumar-yadav1307

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding project template cloning during project creation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/clone-template

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
apps/dashboard-api/src/controllers/project.controller.js (1)

271-333: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Use the required API response envelope for project cloning.

The modified clone flow continues to return and consume raw project objects: the controller returns projectObj, while the dashboard reads res.data._id. Standardize success responses as { success, data, message }, use AppError for errors, and navigate via res.data.data._id.

  • apps/dashboard-api/src/controllers/project.controller.js#L271-L333: return the prescribed success/error envelope from createProject.
  • apps/web-dashboard/src/pages/Templates.jsx#L187-L193: consume the project ID from the envelope’s data field.

As per coding guidelines, “All endpoints must return { success: bool, data: {}, message: "" }; use AppError for errors, never raw throw, and never expose MongoDB errors to clients.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 271 -
333, Update createProject in
apps/dashboard-api/src/controllers/project.controller.js at lines 271-333 to
return the standard { success, data, message } envelope, replace raw errors with
AppError, and avoid exposing MongoDB errors; update the clone response handling
in apps/web-dashboard/src/pages/Templates.jsx at lines 187-193 to read the
project ID from res.data.data._id.

Source: Coding guidelines

packages/common/src/utils/storage.manager.js (1)

354-369: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not require endpoint for AWS S3.

For provider === "s3" without a custom endpoint, targetEndpoint is undefined, so getS3CompatibleStorage() rejects valid S3 configurations even though the dashboard S3 validation does not require an endpoint and the other S3/R2/GCS paths accept an undefined endpoint except where GCS’s default is set.

Proposed fix
-        if (
-            !targetEndpoint ||
-            !config.accessKeyId ||
-            !config.secretAccessKey
-        ) {
+        if (
+            (provider === "cloudflare_r2" && !targetEndpoint) ||
+            !config.accessKeyId ||
+            !config.secretAccessKey
+        ) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/common/src/utils/storage.manager.js` around lines 354 - 369, Update
the validation condition in getS3CompatibleStorage so targetEndpoint is required
only for cloudflare_r2, while preserving GCS’s default endpoint and allowing AWS
S3 to use an undefined endpoint. Keep the existing accessKeyId and
secretAccessKey requirements unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web-dashboard/src/components/Settings/StorageConfigForm.jsx`:
- Around line 31-33: Update the provider-change handling in the storage
configuration effect and the corresponding GCS initialization paths to reset
provider-specific values when switching to GCS. Clear or normalize storageUrl,
publicUrlHost, and any related endpoint state so stale R2 values cannot be
displayed, submitted, or take precedence over GCS defaults; preserve the
existing behavior for other providers.

In `@apps/web-dashboard/src/pages/Templates.jsx`:
- Around line 196-201: Update the clone flow around the finally block in
Templates.jsx so failed requests preserve the projectName and projectDescription
form values and keep the modal available for retry. Move field and modal cleanup
to the successful completion path or explicit cancellation handler, while
retaining isCloning reset for every outcome.
- Around line 439-487: Document the project-cloning workflow in both repository
documentation and the Templates dashboard near the Clone Modal rendered when
cloneTemplate is set. Explain the required projectName input, optional
projectDescription, and that cloning provisions the template’s authentication
setup and collections; keep the existing form behavior unchanged.

---

Outside diff comments:
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 271-333: Update createProject in
apps/dashboard-api/src/controllers/project.controller.js at lines 271-333 to
return the standard { success, data, message } envelope, replace raw errors with
AppError, and avoid exposing MongoDB errors; update the clone response handling
in apps/web-dashboard/src/pages/Templates.jsx at lines 187-193 to read the
project ID from res.data.data._id.

In `@packages/common/src/utils/storage.manager.js`:
- Around line 354-369: Update the validation condition in getS3CompatibleStorage
so targetEndpoint is required only for cloudflare_r2, while preserving GCS’s
default endpoint and allowing AWS S3 to use an undefined endpoint. Keep the
existing accessKeyId and secretAccessKey requirements unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: af4857eb-ae87-4f0a-81ae-c16ca7a276d5

📥 Commits

Reviewing files that changed from the base of the PR and between 4e27fc3 and 90721f3.

📒 Files selected for processing (9)
  • apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js
  • apps/dashboard-api/src/controllers/project.controller.js
  • apps/web-dashboard/src/components/Settings/StorageConfigForm.jsx
  • apps/web-dashboard/src/pages/Templates.jsx
  • mintlify/docs/docs.json
  • packages/common/src/index.js
  • packages/common/src/utils/input.validation.js
  • packages/common/src/utils/storage.manager.js
  • packages/common/src/utils/templates.js

Comment thread apps/web-dashboard/src/components/Settings/StorageConfigForm.jsx
Comment thread apps/web-dashboard/src/pages/Templates.jsx
Comment thread apps/web-dashboard/src/pages/Templates.jsx
@yash-pouranik
yash-pouranik temporarily deployed to feature/clone-template - urBackend-frankfrut PR #367 July 27, 2026 09:12 — with Render Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
apps/dashboard-api/src/controllers/project.controller.js (1)

368-378: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Onboarding-step reset logic has drifted between the primary and retry success paths, and the test suite can't catch it. The primary transaction path resets collectionCreated/firstApiCall in addition to marking projectCreated; the retry (non-transactional) path only marks projectCreated. Because the test suite forces mongoose.startSession to always reject, the primary path — and this drift — is never exercised.

  • apps/dashboard-api/src/controllers/project.controller.js#L368-L378: extract the onboarding-mark + reset + emitEvent + JSON-response logic into a shared helper used by both branches, so they can't diverge again.
  • apps/dashboard-api/src/controllers/project.controller.js#L395-L401: call the same shared helper here instead of the abbreviated inline version that skips the collectionCreated/firstApiCall reset.
  • apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js#L49-L55: add a test scenario (or a separate suite) that exercises the primary transactional success path — e.g. by mocking mongoose.startSession to resolve with a working session stub — so this success-handling logic is covered on both branches.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 368 -
378, Extract the shared onboarding success handling around
markDeveloperOnboardingStep, subsequent-step resets, emitEvent, and the JSON
response into a helper, then invoke it from both project.controller.js sites at
lines 368-378 and 395-401 so retry and transactional paths behave identically.
In project.controller.js lines 395-401, remove the abbreviated inline handling
and use the helper. In
apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js lines
49-55, add coverage with mongoose.startSession resolving to a working session
stub to exercise the transactional success path alongside the retry path.
🧹 Nitpick comments (3)
apps/dashboard-api/src/controllers/project.controller.js (3)

310-312: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove leftover exploratory comments.

These read like in-progress debugging notes ("Wait, does 'users' collection get automatically added...") rather than documentation, and should be cleaned up before merge.

🧹 Proposed cleanup
       if (template.isAuthEnabled) {
         newProject.isAuthEnabled = true;
-        // The default users collection is appended later or explicitly, let's see. 
-        // Wait, does 'users' collection get automatically added if isAuthEnabled is set?
-        // Let's add it explicitly if not present.
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 310 -
312, Remove the leftover exploratory comments near the users collection handling
in the project controller, including the notes about whether the default
collection is appended automatically and adding it explicitly; leave the
surrounding implementation unchanged.

315-327: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Reuse getDefaultRlsForCollection instead of re-deriving ownerField inline.

This block reimplements RLS/ownerField derivation with a simpler rule than the existing getDefaultRlsForCollection (lines 129-148), which already inspects the collection's own schema fields (userId/ownerId) and special-cases users. The inline version here always falls back to ownerField: 'userId' for any non-users collection, ignoring an ownerId field if the template collection has one. For the current sdk-kanban template this happens to match, but a future template collection modeled around ownerId would silently get the wrong ownerField, weakening the RLS enforcement for that collection.

♻️ Proposed refactor
       if (template.collections && template.collections.length > 0) {
         newProject.collections = template.collections.map(col => {
           const mode = typeof col.rls === 'string' ? col.rls : (col.rls?.mode || 'public-read');
+          const defaults = getDefaultRlsForCollection(col.name, col.model);
           return {
             name: col.name,
             model: col.model,
             rls: {
               enabled: mode !== 'public-read',
               mode,
-              ownerField: col.name === 'users' ? '_id' : 'userId',
+              ownerField: defaults.ownerField,
               requireAuthForWrite: true
             }
           };
         });
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 315 -
327, Update the template collection mapping to reuse getDefaultRlsForCollection
for RLS configuration instead of deriving ownerField inline. Preserve the
collection-specific mode handling where needed, while allowing the helper to
inspect each collection schema, special-case users, and select userId or ownerId
correctly.

387-393: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use structured error checking for transaction unsupported errors.

Broad substrings like "standalone", "Session", or "replica set" can match unrelated createProject errors while still re-executing key generation, limit checks, and the save as a no-transaction project. Rely on the driver error shape/code for the transaction error, e.g. "Transaction numbers are only allowed..." plus err.codeName === 'IllegalOperation'/err.code === 20, so only unsupported MongoDB transactions trigger the fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 387 -
393, Update the transaction-error detection in the createProject error-handling
path to rely on MongoDB’s structured error indicators rather than broad
“standalone”, “Session”, or “replica set” message matches. Recognize the
unsupported-transaction case only when the transaction-number message is present
together with the appropriate driver codeName or code, such as IllegalOperation
or 20, before re-executing the non-transaction fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 405-407: Update the error response logic using retryErr so only
AppError instances can provide a client-facing statusCode and message; remove
the fallback to retryErr.status from arbitrary errors. Treat all non-AppError
errors, including MongoDB or other internal errors, as status 500 with the
generic "Internal server error" message.
- Line 404: Update the error-response branches in the surrounding project
controller retry/error handling, including the ZodError and generic failure
paths, to consistently return the mandated { success, data, message } shape.
Replace error properties with data containing the existing issue details, and
add an appropriate empty data object to generic failures while preserving their
status codes and messages.
- Line 383: Update the abortTransaction error handler in the project
controller’s transaction cleanup flow to log the caught error instead of
silently swallowing it. Preserve the existing cleanup behavior while using the
controller’s established logging mechanism and include sufficient context about
the failed session abort.

---

Outside diff comments:
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 368-378: Extract the shared onboarding success handling around
markDeveloperOnboardingStep, subsequent-step resets, emitEvent, and the JSON
response into a helper, then invoke it from both project.controller.js sites at
lines 368-378 and 395-401 so retry and transactional paths behave identically.
In project.controller.js lines 395-401, remove the abbreviated inline handling
and use the helper. In
apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js lines
49-55, add coverage with mongoose.startSession resolving to a working session
stub to exercise the transactional success path alongside the retry path.

---

Nitpick comments:
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 310-312: Remove the leftover exploratory comments near the users
collection handling in the project controller, including the notes about whether
the default collection is appended automatically and adding it explicitly; leave
the surrounding implementation unchanged.
- Around line 315-327: Update the template collection mapping to reuse
getDefaultRlsForCollection for RLS configuration instead of deriving ownerField
inline. Preserve the collection-specific mode handling where needed, while
allowing the helper to inspect each collection schema, special-case users, and
select userId or ownerId correctly.
- Around line 387-393: Update the transaction-error detection in the
createProject error-handling path to rely on MongoDB’s structured error
indicators rather than broad “standalone”, “Session”, or “replica set” message
matches. Recognize the unsupported-transaction case only when the
transaction-number message is present together with the appropriate driver
codeName or code, such as IllegalOperation or 20, before re-executing the
non-transaction fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b9135c78-757e-40dc-9265-ab4dad6f2ba3

📥 Commits

Reviewing files that changed from the base of the PR and between 90721f3 and 4c40587.

📒 Files selected for processing (6)
  • apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js
  • apps/dashboard-api/src/controllers/project.controller.js
  • apps/web-dashboard/src/components/Settings/StorageConfigForm.jsx
  • apps/web-dashboard/src/pages/Templates.jsx
  • mintlify/docs/docs.json
  • packages/common/src/utils/storage.manager.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web-dashboard/src/pages/Templates.jsx
  • apps/web-dashboard/src/components/Settings/StorageConfigForm.jsx
  • packages/common/src/utils/storage.manager.js

Comment thread apps/dashboard-api/src/controllers/project.controller.js Outdated
Comment thread apps/dashboard-api/src/controllers/project.controller.js Outdated
Comment thread apps/dashboard-api/src/controllers/project.controller.js Outdated
… proper mocks, fix AppError arg order & error handler status extraction

- Rewrote cloneTemplate test to use complete @urbackend/common mock (no jest.requireActual) preventing Redis/BullMQ/GC side effects in CI
- Fixed swapped AppError constructor args in createProject project-limit check (was: message, statusCode → now: statusCode, message)
- Fixed error handler using AppError.status (string 'fail'/'error') instead of AppError.statusCode (numeric) for HTTP response
- Added 4 new test cases: blank project, missing name validation, project limit enforcement, users collection verification
- All 19 test suites, 164 tests pass
- Extracted finalizeProjectCreation helper for consistent success responses across transactional and retry paths
- Updated transaction error detection to strictly check MongoDB code 20 or IllegalOperation
- Fixed error response formats to consistently use { success, data, message } (replacing 'error' with 'data' and adding 'data: {}' for generic errors)
- Added error logging for abortTransaction failures
- Reused getDefaultRlsForCollection for template collections RLS setup
- Cleaned up exploratory comments
- Added test coverage for transactional success path
@yash-pouranik
yash-pouranik temporarily deployed to feature/clone-template - urBackend-frankfrut PR #367 July 27, 2026 12:20 — with Render Destroyed
@vercel
vercel Bot temporarily deployed to Preview – urbackend July 27, 2026 12:20 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js (2)

289-310: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Missing negative-path coverage for transaction rollback.

This test only covers the commit/success path (startTransactioncommitTransactionendSession). Given the commit summary calls out fixing "transaction error detection in createProject," a companion test that forces a failure mid-transaction and asserts abortTransaction (not commitTransaction) plus endSession are called — and that the error response still follows the standard shape — would directly validate that fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js`
around lines 289 - 310, Add a companion failure-path test beside the
transactional success test for createProject that makes a mid-transaction
operation reject, then assert abortTransaction and endSession are called,
commitTransaction is not called, and the response uses the standard error
shape/status. Reuse the existing req, res, and mockSession setup and restore any
mocked failure behavior after the test.

249-255: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Error-path tests don't verify the standardized response body.

These tests only check res.status, not that the JSON body follows the { success, data, message } contract expected of all API endpoints, and that no raw/Mongo error details leak through. Given this PR's stated goal of fixing AppError handling and error response formats in createProject, asserting the body shape here would directly validate that fix.

As per coding guidelines: "All API endpoints must return { success: bool, data: {}, message: \"\" }; use AppError for errors, never raw throw, and never expose MongoDB errors to clients."

✅ Suggested additional assertions
   await createProject(req, res);

   expect(res.status).toHaveBeenCalledWith(400);
+  const responseObj = res.json.mock.calls[0][0];
+  expect(responseObj.success).toBe(false);
+  expect(responseObj).toHaveProperty('message');

Also applies to: 257-265

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js`
around lines 249 - 255, Extend the error-path tests for createProject, including
the missing-name case and the additional case around lines 257-265, to assert
the JSON response follows the standardized { success, data, message } contract.
Verify the error response indicates failure, uses an empty data object, provides
the expected safe message, and does not expose raw MongoDB or internal error
details, while retaining the existing 400 status assertions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js`:
- Around line 10-147: Replace the hand-duplicated PROJECT_TEMPLATES,
createProjectSchema, and AppError definitions with imports from their real
`@urbackend/common` submodules, and expose those imports through the
jest.mock('`@urbackend/common`') factory. Keep only side-effecting dependencies
mocked so the tests validate production template, schema, and error contracts.

---

Nitpick comments:
In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js`:
- Around line 289-310: Add a companion failure-path test beside the
transactional success test for createProject that makes a mid-transaction
operation reject, then assert abortTransaction and endSession are called,
commitTransaction is not called, and the response uses the standard error
shape/status. Reuse the existing req, res, and mockSession setup and restore any
mocked failure behavior after the test.
- Around line 249-255: Extend the error-path tests for createProject, including
the missing-name case and the additional case around lines 257-265, to assert
the JSON response follows the standardized { success, data, message } contract.
Verify the error response indicates failure, uses an empty data object, provides
the expected safe message, and does not expose raw MongoDB or internal error
details, while retaining the existing 400 status assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f9cac986-1c97-4233-9b27-957b613958e9

📥 Commits

Reviewing files that changed from the base of the PR and between 4c40587 and dc96277.

📒 Files selected for processing (2)
  • apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js
  • apps/dashboard-api/src/controllers/project.controller.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard-api/src/controllers/project.controller.js

Comment on lines +10 to +147
const PROJECT_TEMPLATES = {
'sdk-kanban': {
name: 'Kanban Board',
description: 'Full-featured Kanban board with drag-and-drop tasks.',
isAuthEnabled: true,
collections: [
{
name: 'boards',
rls: 'private',
model: [{ key: 'title', type: 'String', required: true }],
},
{
name: 'tasks',
rls: 'private',
model: [
{ key: 'boardId', type: 'Ref', required: true },
{ key: 'title', type: 'String', required: true },
{ key: 'status', type: 'String', required: true, default: 'todo' },
],
},
],
},
};

/* ------------------------------------------------------------------ */
/* Real Zod — needed for schema validation inside createProject */
/* ------------------------------------------------------------------ */
const { z } = require('zod');

/* Reconstruct the real createProjectSchema so parse() actually works */
const createProjectSchema = z.object({
name: z.string().min(1, 'Project name is required'),
description: z.string().optional(),
templateId: z.string().optional(),
siteUrl: z.preprocess(
(val) => (val === '' || val === null ? undefined : val),
z
.string()
.url('Invalid Site URL format')
.refine((url) => {
try {
const parsed = new URL(url);
return (
parsed.protocol === 'https:' ||
(parsed.protocol === 'http:' &&
['localhost', '127.0.0.1', '::1'].includes(parsed.hostname))
);
} catch {
return false;
}
}, 'Site URL must use HTTPS (or http://localhost for local development)')
.optional(),
),
});

/* ------------------------------------------------------------------ */
/* Mock @urbackend/common — NO jest.requireActual to avoid side- */
/* effects (Redis, BullMQ queues, GC timers, email service, etc.) */
/* ------------------------------------------------------------------ */
const mockSave = jest.fn();
const mockCountDocuments = jest.fn();

// Minimal Project constructor mock that behaves like a Mongoose model
function MockProject(data) {
Object.assign(this, data);
this._id = data._id || new mongoose.Types.ObjectId();
this.collections = this.collections || [];
this.isAuthEnabled = this.isAuthEnabled || false;
}
MockProject.prototype.save = mockSave;
MockProject.prototype.toObject = function () {
const obj = { ...this };
// Simulate toObject stripping prototype methods
delete obj.save;
delete obj.toObject;
return obj;
};
MockProject.countDocuments = mockCountDocuments;
// For spying / instanceof checks
MockProject.schema = { obj: {} };

jest.mock('@urbackend/common', () => ({
Project: MockProject,
PROJECT_TEMPLATES,
createProjectSchema,
generateApiKey: jest.fn(() => 'test_api_key'),
hashApiKey: jest.fn(() => 'hashed_key'),
encrypt: jest.fn(() => ({ encrypted: 'enc', iv: 'iv', tag: 'tag' })),
markDeveloperOnboardingStep: jest.fn().mockResolvedValue(),
AppError: class AppError extends Error {
constructor(statusCode, message, error = null) {
super(message);
this.name = 'AppError';
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.error = error || (statusCode >= 500 ? 'Internal Server Error' : 'Error');
this.isOperational = true;
}
},
// Stubs for other imports used at module-level by project.controller.js
Developer: { findById: jest.fn() },
Log: { aggregate: jest.fn() },
getStorage: jest.fn(),
getConnection: jest.fn(),
getCompiledModel: jest.fn(),
QueryEngine: jest.fn(),
storageRegistry: {},
webhookQueue: { add: jest.fn() },
enqueueCollectionCleanup: jest.fn(),
syncCollectionCleanup: jest.fn(),
resolveEffectivePlan: jest.fn(() => ({ name: 'free' })),
deleteProjectByApiKeyCache: jest.fn(),
setProjectById: jest.fn(),
getProjectById: jest.fn(),
deleteProjectById: jest.fn(),
isProjectStorageExternal: jest.fn(() => false),
getBucket: jest.fn(() => 'default'),
getPresignedUploadUrl: jest.fn(),
verifyUploadedFile: jest.fn(),
getPublicIp: jest.fn(),
clearCompiledModel: jest.fn(),
createUniqueIndexes: jest.fn(),
ApiAnalytics: { aggregate: jest.fn() },
MailLog: { aggregate: jest.fn() },
getProjectAccessQuery: jest.fn((userId) => ({
$or: [{ owner: userId }, { 'members.user': userId }],
})),
getProjectRole: jest.fn(),
Invitation: { find: jest.fn() },
sanitizeObjectId: jest.fn((v) => v),
sanitizeNonEmptyString: jest.fn((v) => v),
createCollectionSchema: { parse: jest.fn((v) => v) },
editCollectionSchema: { parse: jest.fn((v) => v) },
updateExternalConfigSchema: { parse: jest.fn((v) => v) },
updateAuthProvidersSchema: { parse: jest.fn((v) => v) },
decrypt: jest.fn(() => 'decrypted'),
getS3CompatibleStorage: jest.fn(),
}));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Fully re-implemented mocks risk silent drift from production contracts.

PROJECT_TEMPLATES, createProjectSchema, and AppError are hand-duplicated here instead of pulled from @urbackend/common. If the real packages/common/src/utils/templates.js, input.validation.js, or the real AppError class change, these tests keep passing against stale copies while production behavior silently diverges — defeating the purpose of the "Template creation validation" coverage this PR adds.

Since the side-effecting parts live behind the package's index (Redis/BullMQ/GC timers), consider requiring the specific submodules directly instead of hand-copying their contents:

♻️ Suggested approach
-const PROJECT_TEMPLATES = {
-  'sdk-kanban': { ... },
-};
-const { z } = require('zod');
-const createProjectSchema = z.object({ ... });
+const { PROJECT_TEMPLATES } = require('`@urbackend/common/src/utils/templates`');
+const { createProjectSchema } = require('`@urbackend/common/src/utils/input.validation`');
+const { AppError } = require('`@urbackend/common/src/utils/errors`'); // adjust to actual path

Then reference these real exports inside the jest.mock('@urbackend/common', ...) factory instead of the local re-implementations, keeping only the heavy/side-effecting stubs (Redis, BullMQ, GCS, etc.) mocked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js`
around lines 10 - 147, Replace the hand-duplicated PROJECT_TEMPLATES,
createProjectSchema, and AppError definitions with imports from their real
`@urbackend/common` submodules, and expose those imports through the
jest.mock('`@urbackend/common`') factory. Keep only side-effecting dependencies
mocked so the tests validate production template, schema, and error contracts.

@yash-pouranik
yash-pouranik merged commit 271adc5 into main Jul 27, 2026
11 checks passed
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.

1 participant