Skip to content

feat/issue 46 template details apis - #79

Merged
Basharkhan7776 merged 7 commits into
Openlabsops:mainfrom
David-2610:feat/issue-46-template-details-apis
Jul 1, 2026
Merged

Basharkhan7776 merged 7 commits into
Openlabsops:mainfrom
David-2610:feat/issue-46-template-details-apis

Conversation

@David-2610

@David-2610 David-2610 commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements the [Block] Template/[id] APIs by introducing independent template cloning, paginated reviews, and strict purchase-based review eligibility.

Changes Made

  • Added UserOwnedTemplate and TemplateReview models to the Prisma schema with explicit unique constraints and cascade deletes.
  • Updated POST /api/v1/templates/:id/purchase to transactionally clone the template into an independent, unpublished Form and link it via clonedFormId.
  • Implemented POST /api/v1/templates/:id/reviews with strict validation to ensure only users with a valid purchase receipt (who are not the creator) can submit a review.
  • Refactored GET /api/v1/templates/:id to fetch paginated reviews (?page=&limit=), star aggregations, and viewer context concurrently via Promise.all.
  • Handled race conditions and duplicate actions by correctly trapping Prisma P2002 errors to return 409 Conflict for duplicate purchases or reviews.
  • Added explicit Zod validation (CreateReviewSchema) with .trim() and max(2000) for review text, and strict 1-5 integer bounds for star ratings.

Testing

  • bun run build passes.
  • npx prisma generate succeeds without errors.
  • Manual validation completed.

Related Issues

Checklist

  • Code follows project conventions
  • Tests pass
  • Documentation updated
  • No linting errors

When a user purchases a template, it now creates a completely independent clone, ensuring that future changes by the template creator do not disrupt existing users.
Review eligibility is now strictly tied to purchasing, so only verified users can leave feedback on a template.
Template details now load much faster and support pagination, ensuring the page remains responsive even for templates with thousands of reviews.
Duplicate actions are safely blocked at the database level, preventing users from accidentally purchasing the same template twice or spamming reviews.
The review schema is designed to easily support future features like editing or deleting reviews without requiring major database migrations.

  • Users can purchase a community template to create an independent, editable copy (unpublished) tied to their account.
  • Template reviews are now available with 1–5 star ratings; review text is optional and limited in length.
  • Template detail pages now show review rating summaries and paginated reviews, plus the current viewer’s purchase/ownership/review status.
  • Added safeguards to prevent the template creator from purchasing/reviewing their own content and to block duplicate purchases or duplicate review submissions with clear conflict responses.
  • Updated the data model to track template ownership (via purchases) and reviews, including automatic cleanup when related records are removed.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7ca67f26-9763-4506-a86d-48acb561afde

📥 Commits

Reviewing files that changed from the base of the PR and between 0a6a8ef and efdf0f4.

📒 Files selected for processing (8)
  • apps/api/src/controllers/template.controller.ts
  • apps/api/src/lib/template-schemas.ts
  • apps/api/src/routes/template/template.routes.ts
  • packages/db/prisma/migrations/20260701000000_add_stars_check/migration.sql
  • packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql
  • packages/db/prisma/schema/form.prisma
  • packages/db/prisma/schema/templates.prisma
  • packages/db/prisma/schema/user.prisma
📜 Recent review details
🧰 Additional context used
🪛 Biome (2.5.1)
apps/api/src/controllers/template.controller.ts

[error] 156-156: expected : but instead found const

(parse)


[error] 156-156: expected : but instead found getCommunityTemplates

(parse)


[error] 156-156: expected , but instead found :

(parse)


[error] 474-474: expected ) but instead found }

(parse)


[error] 476-476: expected , but instead found )

(parse)


[error] 477-477: Const declarations must have an initialized value.

(parse)


[error] 477-477: Expected a semicolon or an implicit semicolon after a statement, but found none

(parse)


[error] 477-478: Expected a statement but instead found ')
])'.

(parse)

🪛 SQLFluff (4.2.2)
packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql

[error] 2-2: ADD CONSTRAINT ... FOREIGN KEY should use NOT VALID to avoid locking the table while validating existing rows.

(PG01)

🛑 Comments failed to post (2)
apps/api/src/controllers/template.controller.ts (2)

150-151: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Guard private templates in the detail and purchase queries.

Both lookups only filter by id. Any authenticated user who learns a private template ID can fetch its detail payload and clone it. Treat non-public templates as not found unless the requester is the owner.

Also applies to: 286-287

🤖 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/api/src/controllers/template.controller.ts` around lines 150 - 151, The
template lookup in the detail and purchase flows only filters by id, which
allows private templates to be fetched by anyone who knows the ID. Update the
query logic in template.controller.ts for the affected detail/purchase handlers
to also require the template to be public or owned by the authenticated user,
and return not found when a non-owner requests a private template. Use the
existing templateId lookup and request user context in those controller methods
to apply the access guard consistently in both places.

159-160: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Bound page on the public listing.

skip = (page - 1) * limit still uses an unbounded page, so /community?page=999999999 can trigger very expensive OFFSET scans on an unauthenticated endpoint. Apply the same safe-integer cap used in getTemplateById before computing skip.

🤖 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/api/src/controllers/template.controller.ts` around lines 159 - 160, The
public listing still accepts an unbounded page value, so `skip` in
`TemplateController` can become a huge OFFSET scan; clamp `page` to the same
safe-integer maximum used in `getTemplateById` before computing `skip`. Update
the `page` parsing logic in the listing handler so it uses the bounded value,
then derive `skip = (page - 1) * limit` from that capped page.
🔇 Additional comments (2)
apps/api/src/controllers/template.controller.ts (1)

149-156: 🎯 Functional Correctness

Restore the controller structure before merge.

packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql (1)

2-2: 🩺 Stability & Availability

Confirm the FK deployment path for existing rows. If user_owned_templates already has data, add the constraint as NOT VALID and validate it in a second step to avoid a longer lock during deploy.


📝 Walkthrough

Walkthrough

Adds template detail, purchase, and review endpoints with new Prisma models, review validation, and controller logic for ownership checks, review aggregation, transactional cloning, and duplicate-prevention constraints.

Changes

Template marketplace flow

Layer / File(s) Summary
Data models for reviews and ownership
packages/db/prisma/schema/templates.prisma, packages/db/prisma/schema/user.prisma, packages/db/prisma/schema/form.prisma, packages/db/prisma/migrations/*
Adds TemplateReview and UserOwnedTemplate models, extends template/user/form relations, and adds database constraints for review stars and cloned form ownership.
Review validation and route wiring
apps/api/src/lib/template-schemas.ts, apps/api/src/routes/template/template.routes.ts
Adds CreateReviewSchema and wires authenticated template detail, purchase, and review routes into the router.
Template detail retrieval
apps/api/src/controllers/template.controller.ts
getTemplateById fetches the template with paginated reviews, star stats, and viewer ownership/review state, returning 404 when missing.
Template purchase and cloning
apps/api/src/controllers/template.controller.ts
purchaseTemplate clones a template into a new Form, creates a UserOwnedTemplate record, increments useCount, and handles 400/404/409 cases.
Review creation
apps/api/src/controllers/template.controller.ts
createReview checks template existence and ownership before creating a templateReview, mapping duplicate submissions to 409.

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

Possibly related issues

Possibly related PRs

  • Openlabsops/Snap-form#60: Shares the same templates router/controller area and extends the API surface in the same feature area.
  • Openlabsops/Snap-form#62: Also touches the Form↔UserOwnedTemplate Prisma relationship used by the new purchase flow.
  • Openlabsops/Snap-form#63: Modifies the templates routing path and auth/validation wiring that this PR extends.

Suggested reviewers: Basharkhan7776

Poem

A bunny hops by, so spry and bright,
With stars and reviews all tucked in tight.
Forms get cloned in a tidy spree,
Ownership blooms for you and me,
Hop-hop, the templates sing tonight 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change set: new template details and related APIs for purchase and reviews.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.1)
apps/api/src/controllers/template.controller.ts

File contains syntax errors that prevent linting: Line 156: expected : but instead found const; Line 156: expected : but instead found getCommunityTemplates; Line 156: expected , but instead found :; Line 272: expected , but instead found ;; Line 280: expected , but instead found (; Line 377: Expected an expression but instead found ')'.; Line 377: expected , but instead found ;; Line 386: expected , but instead found (; Line 474: expected ) but instead found }; Line 476: expected , but instead found ); Line 477: Const declarations must have an initialized value.; Line 477: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 477: Expected a statement but instead found ')
])'.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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/api/src/controllers/template.controller.ts`:
- Around line 139-141: The pagination logic in template.controller.ts currently
caps limit but leaves page unbounded, so reviewSkip can become an enormous
OFFSET. Update the page handling in the pagination block to validate page as a
safe positive integer and reject or cap overly large values before computing
reviewSkip, or change the endpoint to cursor-based pagination; keep the fix near
the existing req.query.page, req.query.limit, and reviewSkip calculations so
it’s easy to locate.

In `@packages/db/prisma/schema/templates.prisma`:
- Line 50: Add a database-level constraint for the template review rating field
because `TemplateReview.stars` is only documented as 1–5 in the Prisma schema.
Update the schema/migration around `template_reviews` so invalid values are
rejected at the DB layer, and ensure `avgStars` and related review stats can
only be computed from valid ratings.
- Around line 81-83: `clonedFormId` is currently only a nullable string in the
template schema, so Prisma does not enforce the intended link to `Form`; update
the `clonedFormId` field in the template model to use an optional `Form`
relation with `onDelete: SetNull`, and add the matching back-reference on `Form`
if the schema requires it so referential integrity and delete behavior are
enforced.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: caa2767b-ddf8-4797-85ee-7ae8393b9bf2

📥 Commits

Reviewing files that changed from the base of the PR and between 91871fa and 0a6a8ef.

📒 Files selected for processing (5)
  • apps/api/src/controllers/template.controller.ts
  • apps/api/src/lib/template-schemas.ts
  • apps/api/src/routes/template/template.routes.ts
  • packages/db/prisma/schema/templates.prisma
  • packages/db/prisma/schema/user.prisma
📜 Review details
🔇 Additional comments (5)
packages/db/prisma/schema/templates.prisma (1)

32-42: LGTM!

packages/db/prisma/schema/user.prisma (1)

30-35: LGTM!

apps/api/src/lib/template-schemas.ts (1)

33-46: LGTM!

apps/api/src/controllers/template.controller.ts (1)

145-224: LGTM!

Also applies to: 234-331, 340-427

apps/api/src/routes/template/template.routes.ts (1)

4-28: LGTM!

Comment thread apps/api/src/controllers/template.controller.ts Outdated
Comment thread packages/db/prisma/schema/templates.prisma
Comment thread packages/db/prisma/schema/templates.prisma
- Cap pagination page to 1000 in template controller to prevent large offsets

- Add database-level CHECK constraint for template_reviews stars (1-5)

- Add proper foreign key relation for clonedFormId to Form model
@David-2610

Copy link
Copy Markdown
Contributor Author

@Basharkhan7776
Should we make the textual review mandatory Or leave as it is optional ...?

@Basharkhan7776 Basharkhan7776 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good, resolve the issue will merge.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/api/src/controllers/template.controller.ts`:
- Around line 159-160: The public listing still accepts an unbounded page value,
so `skip` in `TemplateController` can become a huge OFFSET scan; clamp `page` to
the same safe-integer maximum used in `getTemplateById` before computing `skip`.
Update the `page` parsing logic in the listing handler so it uses the bounded
value, then derive `skip = (page - 1) * limit` from that capped page.
- Around line 150-151: The template lookup in the detail and purchase flows only
filters by id, which allows private templates to be fetched by anyone who knows
the ID. Update the query logic in template.controller.ts for the affected
detail/purchase handlers to also require the template to be public or owned by
the authenticated user, and return not found when a non-owner requests a private
template. Use the existing templateId lookup and request user context in those
controller methods to apply the access guard consistently in both places.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7ca67f26-9763-4506-a86d-48acb561afde

📥 Commits

Reviewing files that changed from the base of the PR and between 0a6a8ef and efdf0f4.

📒 Files selected for processing (8)
  • apps/api/src/controllers/template.controller.ts
  • apps/api/src/lib/template-schemas.ts
  • apps/api/src/routes/template/template.routes.ts
  • packages/db/prisma/migrations/20260701000000_add_stars_check/migration.sql
  • packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql
  • packages/db/prisma/schema/form.prisma
  • packages/db/prisma/schema/templates.prisma
  • packages/db/prisma/schema/user.prisma
📜 Review details
🧰 Additional context used
🪛 Biome (2.5.1)
apps/api/src/controllers/template.controller.ts

[error] 156-156: expected : but instead found const

(parse)


[error] 156-156: expected : but instead found getCommunityTemplates

(parse)


[error] 156-156: expected , but instead found :

(parse)


[error] 474-474: expected ) but instead found }

(parse)


[error] 476-476: expected , but instead found )

(parse)


[error] 477-477: Const declarations must have an initialized value.

(parse)


[error] 477-477: Expected a semicolon or an implicit semicolon after a statement, but found none

(parse)


[error] 477-478: Expected a statement but instead found ')
])'.

(parse)

🪛 SQLFluff (4.2.2)
packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql

[error] 2-2: ADD CONSTRAINT ... FOREIGN KEY should use NOT VALID to avoid locking the table while validating existing rows.

(PG01)

🔇 Additional comments (2)
apps/api/src/controllers/template.controller.ts (1)

149-156: 🎯 Functional Correctness

Restore the controller structure before merge.

packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql (1)

2-2: 🩺 Stability & Availability

Confirm the FK deployment path for existing rows. If user_owned_templates already has data, add the constraint as NOT VALID and validate it in a second step to avoid a longer lock during deploy.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🤖 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/api/src/controllers/template.controller.ts`:
- Around line 159-160: The public listing still accepts an unbounded page value,
so `skip` in `TemplateController` can become a huge OFFSET scan; clamp `page` to
the same safe-integer maximum used in `getTemplateById` before computing `skip`.
Update the `page` parsing logic in the listing handler so it uses the bounded
value, then derive `skip = (page - 1) * limit` from that capped page.
- Around line 150-151: The template lookup in the detail and purchase flows only
filters by id, which allows private templates to be fetched by anyone who knows
the ID. Update the query logic in template.controller.ts for the affected
detail/purchase handlers to also require the template to be public or owned by
the authenticated user, and return not found when a non-owner requests a private
template. Use the existing templateId lookup and request user context in those
controller methods to apply the access guard consistently in both places.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7ca67f26-9763-4506-a86d-48acb561afde

📥 Commits

Reviewing files that changed from the base of the PR and between 0a6a8ef and efdf0f4.

📒 Files selected for processing (8)
  • apps/api/src/controllers/template.controller.ts
  • apps/api/src/lib/template-schemas.ts
  • apps/api/src/routes/template/template.routes.ts
  • packages/db/prisma/migrations/20260701000000_add_stars_check/migration.sql
  • packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql
  • packages/db/prisma/schema/form.prisma
  • packages/db/prisma/schema/templates.prisma
  • packages/db/prisma/schema/user.prisma
📜 Review details
🔇 Additional comments (2)
apps/api/src/controllers/template.controller.ts (1)

149-156: 🎯 Functional Correctness

Restore the controller structure before merge.

packages/db/prisma/migrations/20260701000001_add_cloned_form_fk/migration.sql (1)

2-2: 🩺 Stability & Availability

Confirm the FK deployment path for existing rows. If user_owned_templates already has data, add the constraint as NOT VALID and validate it in a second step to avoid a longer lock during deploy.

🛑 Comments failed to post (2)
apps/api/src/controllers/template.controller.ts (2)

150-151: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Guard private templates in the detail and purchase queries.

Both lookups only filter by id. Any authenticated user who learns a private template ID can fetch its detail payload and clone it. Treat non-public templates as not found unless the requester is the owner.

Also applies to: 286-287

🤖 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/api/src/controllers/template.controller.ts` around lines 150 - 151, The
template lookup in the detail and purchase flows only filters by id, which
allows private templates to be fetched by anyone who knows the ID. Update the
query logic in template.controller.ts for the affected detail/purchase handlers
to also require the template to be public or owned by the authenticated user,
and return not found when a non-owner requests a private template. Use the
existing templateId lookup and request user context in those controller methods
to apply the access guard consistently in both places.

159-160: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Bound page on the public listing.

skip = (page - 1) * limit still uses an unbounded page, so /community?page=999999999 can trigger very expensive OFFSET scans on an unauthenticated endpoint. Apply the same safe-integer cap used in getTemplateById before computing skip.

🤖 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/api/src/controllers/template.controller.ts` around lines 159 - 160, The
public listing still accepts an unbounded page value, so `skip` in
`TemplateController` can become a huge OFFSET scan; clamp `page` to the same
safe-integer maximum used in `getTemplateById` before computing `skip`. Update
the `page` parsing logic in the listing handler so it uses the bounded value,
then derive `skip = (page - 1) * limit` from that capped page.

@Basharkhan7776
Basharkhan7776 merged commit e2c1246 into Openlabsops:main Jul 1, 2026
3 checks passed
@Basharkhan7776

Copy link
Copy Markdown
Member

@Basharkhan7776
Should we make the textual review mandatory Or leave as it is optional ...?

Give a summary atleast

@David-2610

Copy link
Copy Markdown
Contributor Author

@Basharkhan7776
Should we make the textual review mandatory Or leave as it is optional ...?

Give a summary atleast

When a user purchases a template and wants to leave a review, should we make it compulsory to write a comment, or should we keep the comment optional and allow them to give only a star rating (1–5 stars) if they want?

@Basharkhan7776

Copy link
Copy Markdown
Member

optional

@Basharkhan7776
Should we make the textual review mandatory Or leave as it is optional ...?

Give a summary atleast

When a user purchases a template and wants to leave a review, should we make it compulsory to write a comment, or should we keep the comment optional and allow them to give only a star rating (1–5 stars) if they want?

@coderabbitai coderabbitai Bot mentioned this pull request Aug 9, 2026
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