Skip to content

Conversation

@Lobito8
Copy link

@Lobito8 Lobito8 commented Aug 30, 2025

🚀 Volunchain Pull Request

Mark with an x all the checkboxes that apply (like [x])

  • Closes #
  • Added tests (if necessary)
  • Run tests
  • Run formatting
  • Evidence attached
  • Commented the code

📌 Type of Change

  • Documentation (updates to README, docs, or comments)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

📝 Changes description

Add readme with information of arquitecture of module photo, the reason of implement adapter, is the logic of photo is basic and it's not required use cases
Add adapter to photos.
Migrate to supabaseStorage to image.

⏰ Time spent breakdown

3 hours

Summary by CodeRabbit

  • New Features

    • Introduced Supabase-backed photo storage and service.
    • Added endpoints to upload photos, fetch by ID, delete, and update metadata.
    • Input validation for userId and photo IDs with clear error messages.
  • Documentation

    • Added Photos Module README detailing structure and usage.
  • Refactor

    • Reworked photo domain model and controllers; updated routes.
    • Improved validation middleware instantiation.
    • Streamlined server startup logging.
  • Chores

    • Removed unused cloud storage dependencies (S3, Backblaze B2) from the project.

@coderabbitai
Copy link

coderabbitai bot commented Aug 30, 2025

Walkthrough

Removes S3/Backblaze/Multer deps and legacy Backblaze/repository code. Introduces a Supabase-based photo module with domain entity, interfaces, exceptions, DTOs, controller, and routes. Updates validation middleware to plainToInstance. Minor log formatting and an import/typing tweak in organization controller. Adds photo module README.

Changes

Cohort / File(s) Summary of changes
Dependencies cleanup
package.json
Removed dependencies: @aws-sdk/client-s3, backblaze-b2, multer.
Server log formatting
src/index.ts
Reformatted a logger.info call to single line; no logic change.
Organization controller typing/imports
src/modules/organization/presentation/controllers/organization.controller.ts
Switched DTO import to alias path; relaxed Request typing for getOrganizationById from Request<UuidParamsDto> to Request.
Photo domain: interfaces, entity, exceptions
src/modules/photo/domain/entities/interfaces/photo.interface.ts, src/modules/photo/domain/entities/photo.entity.ts, src/modules/photo/domain/exceptions/domain.exception.ts
Added storage/photo interfaces; replaced ORM-based entity with plain PhotoEntity using new types and domain exceptions; added InvalidPhotoUrlException and MissingUserIdException.
Photo infra: adapters (Supabase) and removals
src/modules/photo/infrastructure/adapters/interface/photo-service.adapter.ts, src/modules/photo/infrastructure/adapters/supabase-service.adapter.ts, src/modules/photo/infrastructure/services/BackblazeService.ts
Added IPhotoServiceAdapter and Supabase implementation handling upload/get/delete/update via Supabase Storage and Prisma; removed Backblaze service.
Photo presentation: controller, routes, DTOs, docs
src/modules/photo/presentation/controllers/photo-controller.ts, src/modules/photo/presentation/routes.ts, src/modules/photo/presentation/dto/*, src/modules/photo/README.md, src/modules/photo/presentation/controllers/PhotoController.ts
Added controller with handlers, express routes, DTOs for upload/get/delete, and README; removed legacy PhotoController using Multer/prisma inline.
Photo interfaces and re-exports cleanup
src/modules/photo/interfaces/photo.interface.ts, src/modules/photo/entities/photo.entity.ts
Removed legacy IPhoto/IPhotoProps interfaces and domain re-export shim.
Repository layer removal
src/repository/IPhotoRepository.ts, src/repository/PhotoRepository.ts
Deleted Photo repository interface and Prisma implementation.
Validation middleware update
src/shared/middleware/validation.middleware.ts
Replaced plainToClass with plainToInstance; adjusted query mutation to clear-and-merge.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Router as Express Router (/photo)
  participant Controller as PhotoController
  participant Service as SupabasePhotoService
  participant Storage as Supabase Storage
  participant DB as Prisma (photos)

  rect rgb(245,248,250)
  note over Client,DB: Upload flow
  Client->>Router: POST /upload (file, userId)
  Router->>Controller: uploadPhoto(req,res)
  Controller->>Service: upload(file, userId, metadata?)
  Service->>Storage: upload(fileName, buffer, contentType)
  Storage-->>Service: public URL or error
  Service->>DB: create photo {userId,url,metadata,uploadedAt}
  DB-->>Service: created record
  Service-->>Controller: PhotoEntity
  Controller-->>Client: 201 Created (photo)
  end

  rect rgb(250,247,245)
  note over Client,DB: Get / Delete flow
  Client->>Router: GET /:id or DELETE /:id
  Router->>Controller: getPhotoById / deletePhoto
  Controller->>Service: getById(id)
  Service->>DB: find photo by id
  DB-->>Service: record or null
  alt found
    Controller-->>Client: 200 OK (get) / proceed (delete)
    Controller->>Service: delete(id) [DELETE only]
    Service->>DB: find & delete record
    Service->>Storage: remove(fileName)
    Storage-->>Service: ack
    Service-->>Controller: done
    Controller-->>Client: 204 No Content [DELETE]
  else not found
    Controller-->>Client: 404 Not Found
  end
  end

  rect rgb(245,250,247)
  note over Client,DB: Update metadata flow
  Client->>Router: PATCH /:id/metadata
  Router->>Controller: updatePhotoMetadata
  Controller->>Service: updateMetadata(id, metadata)
  Service->>DB: update metadata
  DB-->>Service: updated metadata
  Service-->>Controller: updated metadata
  Controller-->>Client: 200 OK (metadata)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Villarley

Poem

I thump my paws—new photos fly!
Supabase skies, we wave goodbye
To clouds of old, Backblaze and S3—
A leaner warren, hopping free.
DTOs line burrow halls with cheer,
Metadata carrots—crunch! So clear.
Ship it swift; the path is here. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 21

Caution

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

⚠️ Outside diff range comments (2)
src/index.ts (1)

168-169: Bug: messages router mounted on the NFT router (wrong path)

This nests messages under /nft/messages instead of /messages. Mount on app.

Apply:

-router.use("/messages", messageRoutes);
+app.use("/messages", messageRoutes);
src/modules/photo/domain/entities/photo.entity.ts (1)

41-48: create() ignores id and uploadedAt; returned entity lacks persistence identity.

This breaks downstream flows (e.g., delete by id) and misreports uploadedAt.

Apply:

   public static create(props: IPhotoProps): PhotoEntity {
     const photo = new PhotoEntity();
+    if (props.id) photo.id = props.id;
     photo.url = props.url;
     photo.userId = props.userId;
     photo.metadata = props.metadata ?? {};
+    if (props.uploadedAt) {
+      // BaseEntity should expose createdAt; adjust if different.
+      (photo as any).createdAt = props.uploadedAt;
+    }
     photo.validate();
     return photo;
   }

If BaseEntity exposes a different timestamp field, map accordingly.

🧹 Nitpick comments (11)
src/index.ts (1)

75-84: Error handler is registered before routes

Express error middleware should come after routes to reliably catch downstream errors.

Move the error-handler block to after all app.use(...) route registrations (right before DB init). Example:

-// Error handler middleware
-app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
-  errorHandler(err, req, res, next);
-});
...
-// API Routes...
+// API Routes...
 app.use("/test", testRoutes);
+
+// Error handler middleware (must be last in the middleware chain)
+app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
+  errorHandler(err, req, res, next);
+});
src/modules/organization/presentation/controllers/organization.controller.ts (1)

40-41: Preserve param typing for stronger guarantees

Reverting to Request keeps compile-time safety and aligns with DTO validation.

Apply:

-  async (req: Request, res: Response): Promise<void> => {
+  async (req: Request<UuidParamsDto>, res: Response): Promise<void> => {
src/shared/middleware/validation.middleware.ts (1)

120-121: Assigning typed params back to req.params

Casting to Record<string, string> hides actual numeric types after conversion. If you rely on numbers (e.g., Photo DTOs), keep runtime types.

Apply one of:

  • Assign without lying about types:
-      req.params = dto as Record<string, string>;
+      // Preserve runtime types; Express typing stays opaque here
+      req.params = dto as any;
  • Or attach validated data separately to avoid mutating Express fields:
// declare a Request augmentation (e.g., req.validated = { params: dto })
src/modules/photo/README.md (2)

8-13: Clarify and fix grammar in “Key Architectural Differences.”

The paragraph is hard to parse and has grammar issues. Suggested rewrite:

-No use cases or repositories are used, only maintained Entity to get easy access on this information, but this Entity is generate for mi adapter, adapter interface required a return of PhotoEntity.
+We do not use traditional use-cases or repositories here. We keep a single Entity for simple access to photo data. The storage adapter’s interface returns a PhotoEntity instance, and the concrete adapter constructs it.

26-36: Fix path typos in structure block.

  • domain/entititesdomain/entities
  • Consider tightening comments:
-    domain/entities/interfaces/ #interfaces of entity
-    domain/entitites/photo.entity.ts #Entity
+    domain/entities/interfaces/  # entity interfaces
+    domain/entities/photo.entity.ts  # PhotoEntity
src/modules/photo/presentation/routes.ts (2)

13-19: Optional: validate before upload?

For multipart, body fields arrive with the file. If you want to reject early without buffering the file, you’ll need a light multipart field parser. Otherwise, current order (multer → validate) is acceptable.


8-12: Service/controller contract drift to watch.

Ensure SupabasePhotoService.upload returns a Photo with correct id (DB id, not URL) and that controller/clients rely on that. The current adapter snippet sets id = photo.url — that will break DELETE/GET by id.

I can open a follow-up PR adjusting the adapter return mapping.

src/modules/photo/infrastructure/adapters/interface/photo-service.adapter.ts (1)

4-16: Decouple from Express/Multer in the adapter interface.

Keeping Express.Multer.File here leaks transport concerns into infra contracts. Consider a minimal input type (buffer, mimeType, originalName, size) instead.

Example:

-export interface IPhotoServiceAdapter {
-  upload(
-    file: Express.Multer.File,
+type UploadInput = { buffer: Buffer; mimeType: string; originalName: string; size: number };
+export interface IPhotoServiceAdapter {
+  upload(
+    file: UploadInput,
     userId: string,
     metadata?: IPhotoMetadata
   ): Promise<PhotoEntity>;
src/modules/photo/presentation/controllers/photo-controller.ts (2)

20-29: Parse optional metadata and pass it to the service.

Currently metadata in body (if any) is ignored.

Apply:

-      const { userId } = req.body;
+      const { userId, metadata } = req.body ?? {};
...
-      const photo = await this.supabasePhotoService.upload(req.file, userId);
+      const photo = await this.photoService.upload(req.file, userId, metadata);

32-47: Authorization check is missing.

Ensure the requester owns the photo (or has proper scope) before returning or mutating resources.

Suggested approach:

  • Inject current userId from auth middleware.
  • Compare with photo.userId (extend getById to return it) or check via DB.
  • Return 403 when unauthorized.
src/modules/photo/infrastructure/adapters/supabase-service.adapter.ts (1)

1-6: Consistent imports and path aliases.

Mixing "@/..." and relative "../../..." hurts maintainability. Standardize on one (prefer aliases).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 23a1f32 and 48e759d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • package.json (0 hunks)
  • src/index.ts (1 hunks)
  • src/modules/organization/presentation/controllers/organization.controller.ts (2 hunks)
  • src/modules/photo/README.md (1 hunks)
  • src/modules/photo/domain/entities/interfaces/photo.interface.ts (1 hunks)
  • src/modules/photo/domain/entities/photo.entity.ts (2 hunks)
  • src/modules/photo/domain/exceptions/domain.exception.ts (1 hunks)
  • src/modules/photo/entities/photo.entity.ts (0 hunks)
  • src/modules/photo/infrastructure/adapters/interface/photo-service.adapter.ts (1 hunks)
  • src/modules/photo/infrastructure/adapters/supabase-service.adapter.ts (1 hunks)
  • src/modules/photo/infrastructure/services/BackblazeService.ts (0 hunks)
  • src/modules/photo/interfaces/photo.interface.ts (0 hunks)
  • src/modules/photo/presentation/controllers/PhotoController.ts (0 hunks)
  • src/modules/photo/presentation/controllers/photo-controller.ts (1 hunks)
  • src/modules/photo/presentation/dto/delete-photo.dto.ts (1 hunks)
  • src/modules/photo/presentation/dto/get-photo.dto.ts (1 hunks)
  • src/modules/photo/presentation/dto/index.ts (1 hunks)
  • src/modules/photo/presentation/dto/upload-photo.dto.ts (1 hunks)
  • src/modules/photo/presentation/routes.ts (1 hunks)
  • src/repository/IPhotoRepository.ts (0 hunks)
  • src/repository/PhotoRepository.ts (0 hunks)
  • src/shared/middleware/validation.middleware.ts (5 hunks)
💤 Files with no reviewable changes (7)
  • src/repository/PhotoRepository.ts
  • src/modules/photo/infrastructure/services/BackblazeService.ts
  • package.json
  • src/modules/photo/interfaces/photo.interface.ts
  • src/repository/IPhotoRepository.ts
  • src/modules/photo/entities/photo.entity.ts
  • src/modules/photo/presentation/controllers/PhotoController.ts
🧰 Additional context used
🧬 Code graph analysis (8)
src/modules/photo/infrastructure/adapters/interface/photo-service.adapter.ts (2)
src/modules/photo/domain/entities/interfaces/photo.interface.ts (1)
  • IPhotoMetadata (6-30)
src/modules/photo/domain/entities/photo.entity.ts (1)
  • PhotoEntity (8-60)
src/modules/photo/presentation/dto/get-photo.dto.ts (1)
src/modules/photo/presentation/dto/index.ts (1)
  • GetPhotoDto (2-2)
src/modules/photo/presentation/dto/delete-photo.dto.ts (1)
src/modules/photo/presentation/dto/index.ts (1)
  • DeletePhotoDto (1-1)
src/modules/photo/presentation/routes.ts (6)
src/modules/photo/infrastructure/adapters/supabase-service.adapter.ts (2)
  • upload (9-55)
  • SupabasePhotoService (7-105)
src/modules/photo/presentation/controllers/photo-controller.ts (1)
  • PhotoController (10-106)
src/shared/middleware/validation.middleware.ts (1)
  • validateDto (15-51)
src/modules/photo/presentation/dto/upload-photo.dto.ts (1)
  • UploadPhotoDto (3-6)
src/modules/photo/presentation/dto/delete-photo.dto.ts (1)
  • DeletePhotoDto (3-6)
src/modules/photo/presentation/dto/get-photo.dto.ts (1)
  • GetPhotoDto (3-6)
src/modules/photo/infrastructure/adapters/supabase-service.adapter.ts (4)
src/modules/photo/infrastructure/adapters/interface/photo-service.adapter.ts (1)
  • IPhotoServiceAdapter (4-16)
src/modules/photo/domain/entities/interfaces/photo.interface.ts (1)
  • IPhotoMetadata (6-30)
src/modules/photo/domain/entities/photo.entity.ts (1)
  • PhotoEntity (8-60)
src/config/supabase.ts (1)
  • supabase (6-6)
src/modules/photo/presentation/dto/upload-photo.dto.ts (1)
src/modules/photo/presentation/dto/index.ts (1)
  • UploadPhotoDto (3-3)
src/modules/photo/presentation/controllers/photo-controller.ts (1)
src/modules/photo/infrastructure/adapters/supabase-service.adapter.ts (1)
  • SupabasePhotoService (7-105)
src/modules/photo/domain/entities/photo.entity.ts (2)
src/modules/photo/domain/entities/interfaces/photo.interface.ts (2)
  • IPhotoMetadata (6-30)
  • IPhotoProps (32-38)
src/modules/photo/domain/exceptions/domain.exception.ts (2)
  • InvalidPhotoUrlException (3-7)
  • MissingUserIdException (9-13)
🪛 LanguageTool
src/modules/photo/README.md

[grammar] ~1-~1: There might be a mistake here.
Context: # 📸 Photos Module This module handles uploading, fetching,...

(QB_NEW_EN)


[grammar] ~6-~6: There might be a mistake here.
Context: ...Ls). # 🔑 Key Architectural Differences No use cases or repositories are used, o...

(QB_NEW_EN)


[grammar] ~8-~8: There might be a mistake here.
Context: ...erface required a return of PhotoEntity. The flow is more direct: Controller → c...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...pabase Storage. Simplified Controllers: Controllers use asyncHandler, validators...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...ter for file processing. # 📂 Structure text modules/ photo/ domain/entities/interfaces/ #interfaces of entity domain/entitites/photo.entity.ts #Entity infrastructure/ presentation/controllers/ presentation/dto routes.ts README.md

(QB_NEW_EN)

🔇 Additional comments (10)
src/index.ts (1)

189-192: Startup log reformat: LGTM

Single-line log with structured metadata keeps parity and improves readability.

src/modules/organization/presentation/controllers/organization.controller.ts (1)

11-11: Import alias change: LGTM

Path alias to "@/shared/dto/base.dto" is consistent with the repo refactor.

src/shared/middleware/validation.middleware.ts (1)

80-82: In-place query mutation: OK, but be mindful of type shapes

This preserves req.query reference; good. Just note downstream code may now see non-string values (numbers/booleans).

Please confirm no consumers assume req.query values are always strings.

src/modules/photo/README.md (1)

17-20: Align file/interface names with actual paths.

If the contract file lives at infrastructure/adapters/interface/photo-service.adapter.ts, rename the reference from photo-service.adapter.interface.ts to the actual path to avoid confusion. Also state explicitly which interface is the canonical contract (domain vs infrastructure).

src/modules/photo/domain/entities/interfaces/photo.interface.ts (1)

32-38: Unify userId and id types across layers.

Here userId is string and id is string. DTOs currently validate them as integers and routes read id from params. Align types with persistence and service APIs (which use string). Update DTOs accordingly (see route comment).

Also applies to: 40-49

src/modules/photo/presentation/dto/index.ts (1)

1-3: LGTM.

Barrel re-exports are correct and minimal.

src/modules/photo/domain/exceptions/domain.exception.ts (2)

3-13: LGTM: focused, domain-specific exceptions.

Clear messages, extend the shared base, no side effects. Good.


1-1: Import path is correct. The DomainException file resides at src/modules/shared/domain/exceptions/domain.exception.ts, so the relative import ../../../shared/domain/exceptions/domain.exception is valid.

Likely an incorrect or invalid review comment.

src/modules/photo/domain/entities/photo.entity.ts (2)

15-29: Validation is fine.

Covers empty/invalid URL and missing userId.


1-1: No action needed: BaseEntity import resolves correctly
The relative path "../../../shared/domain/entities/base.entity" points to src/modules/shared/domain/entities/base.entity.ts; no update required.

Comment on lines +1 to +4
export interface IStorageAdapter {
upload(file: Buffer, filename: string): Promise<{ url: string; key: string }>;
delete(key: string): Promise<void>;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove or relocate IStorageAdapter; it conflicts with the actual adapter contract.

Domain-level IStorageAdapter couples to Node Buffer and returns {url,key}, while SupabasePhotoService.upload(file: Express.Multer.File, userId: string, metadata?) → PhotoEntity uses a different shape. Keep the storage contract in infrastructure (e.g., IPhotoServiceAdapter) and delete this interface from domain to avoid duplication and drift. If a domain contract is required, make it framework-agnostic (no Buffer/Express types) and align signatures.

-export interface IStorageAdapter {
-  upload(file: Buffer, filename: string): Promise<{ url: string; key: string }>;
-  delete(key: string): Promise<void>;
-}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface IStorageAdapter {
upload(file: Buffer, filename: string): Promise<{ url: string; key: string }>;
delete(key: string): Promise<void>;
}
🤖 Prompt for AI Agents
In src/modules/photo/domain/entities/interfaces/photo.interface.ts lines 1-4:
remove this domain-level IStorageAdapter (delete the file or its export) because
it couples to Node Buffer and a {url,key} shape that conflicts with
SupabasePhotoService; instead move or recreate the adapter contract under the
infrastructure layer (e.g., src/modules/photo/infrastructure/ with a name like
IPhotoServiceAdapter) and update all imports to point to the new infra
interface; if the domain truly needs an interface, replace it with a
framework-agnostic contract (no Buffer/Express types) whose signature accepts an
opaque binary type or stream and returns a domain PhotoEntity (or a
domain-friendly DTO), and ensure SupabasePhotoService implements the infra
adapter signature so signatures no longer drift.

Comment on lines +10 to +12
getById(photoId: string): Promise<{ id: string; url: string } | null>;
delete(photoUrl: string): Promise<void>;
updateMetadata(
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Align delete() contract to operate by photoId (not URL).

Implementation and controller use an id. The interface should reflect that to avoid confusion at the boundary.

Apply:

-  delete(photoUrl: string): Promise<void>;
+  delete(photoId: string): Promise<void>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getById(photoId: string): Promise<{ id: string; url: string } | null>;
delete(photoUrl: string): Promise<void>;
updateMetadata(
getById(photoId: string): Promise<{ id: string; url: string } | null>;
delete(photoId: string): Promise<void>;
updateMetadata(
🤖 Prompt for AI Agents
In src/modules/photo/infrastructure/adapters/interface/photo-service.adapter.ts
around lines 10 to 12, the delete method signature currently accepts a photoUrl
but the implementation and controller operate with photoId; change the interface
to delete(photoId: string): Promise<void> and update any dependent types/usages
to expect an id (not a URL). Ensure method name and parameter name in
implementations, controllers, and unit tests are updated to match the new
signature and run tests/TS compile to catch call sites.

Comment on lines +10 to +13
file: Express.Multer.File,
userId: string,
metadata?: IPhotoMetadata
): Promise<PhotoEntity> {
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Multer memory storage prerequisite.

upload() relies on file.buffer; ensure Multer uses memoryStorage for the photo route.

Run:


🏁 Script executed:

#!/bin/bash
rg -nP 'multer\\s*\\(|memoryStorage\\s*\\(' -C2 src

Length of output: 126


🏁 Script executed:

#!/bin/bash
rg -nP 'import\s+multer' -C2 src
rg -nP 'multer\s*\(\s*{[^}]*storage' -C2 src
rg -nP 'memoryStorage\s*\(' -C2 src

Length of output: 493


Configure Multer with memoryStorage for photo uploads
upload() in supabase-service.adapter.ts reads file.buffer, which is only populated when Multer is using memoryStorage. In src/modules/photo/presentation/routes.ts, Multer is imported but not configured—update it to:

// src/modules/photo/presentation/routes.ts
-import multer from "multer";
-const upload = multer();
+import multer from "multer";
+const upload = multer({ storage: multer.memoryStorage() });

and continue using upload.single('file') (or the appropriate variant) for the photo route.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
file: Express.Multer.File,
userId: string,
metadata?: IPhotoMetadata
): Promise<PhotoEntity> {
// src/modules/photo/presentation/routes.ts
import multer from "multer";
const upload = multer({ storage: multer.memoryStorage() });
// then in your route:
router.post(
"/photos",
upload.single("file"),
photoController.upload.bind(photoController)
);
🤖 Prompt for AI Agents
In src/modules/photo/infrastructure/adapters/supabase-service.adapter.ts around
lines 10 to 13, the upload() implementation expects file.buffer (Multer
memoryStorage) but the photo route imports Multer without configuring it; update
src/modules/photo/presentation/routes.ts to import multer, create an uploader
using multer({ storage: multer.memoryStorage() }), and use that uploader (e.g.,
upload.single('file')) on the photo upload route so req.file.buffer is populated
for the adapter.

Comment on lines +14 to +22
const fileName = `${Date.now()}-${file.originalname}`;

// Subir archivo a Supabase Storage
const { error: uploadError } = await supabase.storage
.from("photos")
.upload(fileName, file.buffer, {
contentType: file.mimetype,
upsert: false,
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use robust, sanitized object keys; avoid timestamp+originalname.

Risk of collisions and path injection via originalname. Use UUID and keep an extension whitelist; optionally namespace by userId.

Apply:

+import { randomUUID } from "crypto";
+import path from "path";
...
-    const fileName = `${Date.now()}-${file.originalname}`;
+    const ext = (path.extname(file.originalname) || ".bin").toLowerCase();
+    const safeExt = [".jpg", ".jpeg", ".png", ".webp", ".gif", ".heic", ".bin"].includes(ext) ? ext : ".bin";
+    const fileName = `${userId}/${randomUUID()}${safeExt}`;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/modules/photo/infrastructure/adapters/supabase-service.adapter.ts around
lines 14 to 22, the current filename generation uses Date.now() + originalname
which risks collisions and path injection; replace this by generating a secure
UUID-based filename, derive and validate the file extension against a whitelist
(reject or map disallowed extensions), and construct the stored key as a safe
namespace (e.g., `${userId || 'anon'}/${uuid}.${ext}`) rather than using the raw
originalname; ensure you never include slashes or unsafe characters from
originalname, and keep upsert:false as before.

Comment on lines +27 to +33
const { data: publicUrl } = supabase.storage
.from("photos")
.getPublicUrl(fileName);

if (!publicUrl) throw new Error("Could not get public URL");

// Guardar en DB con Prisma
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden public URL retrieval and error handling.

Check the error object and the presence of data.publicUrl explicitly.

Apply:

-    const { data: publicUrl } = supabase.storage
+    const { data: publicUrlData, error: publicUrlError } = supabase.storage
       .from("photos")
       .getPublicUrl(fileName);
-
-    if (!publicUrl) throw new Error("Could not get public URL");
+    if (publicUrlError || !publicUrlData?.publicUrl) {
+      throw new Error("Could not get public URL");
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data: publicUrl } = supabase.storage
.from("photos")
.getPublicUrl(fileName);
if (!publicUrl) throw new Error("Could not get public URL");
// Guardar en DB con Prisma
const { data: publicUrlData, error: publicUrlError } = supabase.storage
.from("photos")
.getPublicUrl(fileName);
if (publicUrlError || !publicUrlData?.publicUrl) {
throw new Error("Could not get public URL");
}
// Guardar en DB con Prisma
🤖 Prompt for AI Agents
In src/modules/photo/infrastructure/adapters/supabase-service.adapter.ts around
lines 27 to 33, the current code assumes the supabase response contains a valid
data.publicUrl and throws a generic error; update it to inspect the response
shape: check the returned error object from supabase and validate that data &&
data.publicUrl exist, and if not throw an Error containing the supabase error
message (or the response object serialized) and the fileName for context; ensure
subsequent code uses data.publicUrl (not the whole data) and avoid accessing
undefined properties.

Comment on lines +21 to +23
router.delete("/:id", validateDto(DeletePhotoDto), photoController.deletePhoto);

router.get("/:id", validateDto(GetPhotoDto), photoController.getPhotoById);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Validation applied to body, but id is in params → routes will 400/skip validation incorrectly.

validateDto reads req.body; :id lives in req.params. Create a params validator and use it here. Also, ids are string in the service/DB.

Apply this change in routes:

-router.delete("/:id", validateDto(DeletePhotoDto), photoController.deletePhoto);
-router.get("/:id", validateDto(GetPhotoDto), photoController.getPhotoById);
+router.delete("/:id", validateParamsDto(DeletePhotoParamsDto), photoController.deletePhoto);
+router.get("/:id", validateParamsDto(GetPhotoParamsDto), photoController.getPhotoById);

Add this shared middleware:

export function validateParamsDto<T extends object>(dtoClass: new () => T) {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      const dto = plainToInstance(dtoClass, req.params);
      const errors = await validate(dto);
      if (errors.length) {
        res.status(400).json({ success: false, error: "Validation failed", details: errors });
        return;
      }
      req.params = dto as any;
      next();
    } catch {
      res.status(500).json({ success: false, error: "Internal server error during validation" });
    }
  };
}

And DTOs:

// delete-photo-params.dto.ts
import { IsString } from "class-validator";
export class DeletePhotoParamsDto { @IsString() id!: string; }

// get-photo-params.dto.ts
import { IsString } from "class-validator";
export class GetPhotoParamsDto { @IsString() id!: string; }
🤖 Prompt for AI Agents
In src/modules/photo/presentation/routes.ts around lines 21-23, the current
validateDto middleware validates req.body but the :id param lives in req.params,
causing incorrect 400s; replace validateDto for these routes with a params
validator that validates req.params and casts it back to req.params as the DTO.
Add a shared middleware validateParamsDto that uses plainToInstance on
req.params, runs class-validator, returns 400 with details on validation errors
or 500 on exceptions, and assigns the validated dto back to req.params; create
DeletePhotoParamsDto and GetPhotoParamsDto with @IsString() id fields (IDs are
strings in service/DB) and use validateParamsDto(DeletePhotoParamsDto) for
DELETE "/:id" and validateParamsDto(GetPhotoParamsDto) for GET "/:id".

Comment on lines +21 to +23
Simplified Controllers:
Controllers use asyncHandler, validators, and Multer for file processing.

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm Multer is a declared dependency.

Routes import Multer. Ensure multer (and @types/multer if TS) are present in package.json to avoid runtime/TS errors.

Run:


🏁 Script executed:

#!/bin/bash
jq -r '.dependencies, .devDependencies' package.json | rg -n 'multer'

Length of output: 101


Add Multer to package.json dependencies
Only @types/multer is declared; add "multer": "⟨latest-version⟩" under dependencies in package.json to prevent runtime/TS errors.

🧰 Tools
🪛 LanguageTool

[grammar] ~21-~21: There might be a mistake here.
Context: ...pabase Storage. Simplified Controllers: Controllers use asyncHandler, validators...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In src/modules/photo/README.md around lines 21 to 23, the note mentions Multer
but package.json only declares @types/multer; update package.json by adding a
runtime dependency for multer (e.g. add "multer": "<latest-version>" under
dependencies) so the library is installed at runtime and TypeScript typings
remain compatible; ensure package.json dependencies include the multer entry and
run npm/yarn install to update lockfiles.

): Promise<void> => {
try {
const dto = plainToClass(dtoClass, req.body);
const dto = plainToInstance(dtoClass, req.body);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implicit conversion for body DTOs

Enable class-transformer implicit conversion so numeric/boolean types (e.g., id) are correctly coerced.

Apply:

-      const dto = plainToInstance(dtoClass, req.body);
+      const dto = plainToInstance(dtoClass, req.body, { enableImplicitConversion: true });

Optionally add whitelist to validation to drop unknown fields:

-      const errors = await validate(dto);
+      const errors = await validate(dto, { whitelist: true });
🤖 Prompt for AI Agents
In src/shared/middleware/validation.middleware.ts around line 22,
plainToInstance is called without enabling class-transformer implicit conversion
so numeric/boolean body fields aren't coerced; update the plainToInstance call
to pass class-transformer options with enableImplicitConversion: true so types
like id are converted, and when calling validate pass validation options (e.g.,
{ whitelist: true }) to strip unknown fields if desired — ensure you only add
these options and do not change DTO classes.

Comment on lines +60 to 61
const dto = plainToInstance(dtoClass, req.query);
const errors = await validate(dto);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implicit conversion for query DTOs

Same rationale as body; many query params arrive as strings.

Apply:

-      const dto = plainToInstance(dtoClass, req.query);
+      const dto = plainToInstance(dtoClass, req.query, { enableImplicitConversion: true });

(Optional) Keep whitelist:

-      const errors = await validate(dto);
+      const errors = await validate(dto, { whitelist: true });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const dto = plainToInstance(dtoClass, req.query);
const errors = await validate(dto);
const dto = plainToInstance(dtoClass, req.query, { enableImplicitConversion: true });
const errors = await validate(dto, { whitelist: true });
🤖 Prompt for AI Agents
In src/shared/middleware/validation.middleware.ts around lines 60-61, the query
DTO conversion currently treats all query params as strings; convert and
validate them correctly by using plainToInstance with enableImplicitConversion
set to true so numeric/boolean types are coerced, then call validate with the
same whitelist options you use for body DTOs (e.g., whitelist: true, optionally
forbidNonWhitelisted) to strip/forbid unexpected props; update the
plainToInstance and validate calls accordingly to enable implicit conversion and
keep the whitelist behavior.

Comment on lines +100 to 101
const dto = plainToInstance(dtoClass, req.params);
const errors = await validate(dto);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implicit conversion for params DTOs

Enable coercion so route params like ":id" can be numbers.

Apply:

-      const dto = plainToInstance(dtoClass, req.params);
+      const dto = plainToInstance(dtoClass, req.params, { enableImplicitConversion: true });

(Optional) Whitelist:

-      const errors = await validate(dto);
+      const errors = await validate(dto, { whitelist: true });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const dto = plainToInstance(dtoClass, req.params);
const errors = await validate(dto);
const dto = plainToInstance(dtoClass, req.params, { enableImplicitConversion: true });
const errors = await validate(dto, { whitelist: true });
🤖 Prompt for AI Agents
In src/shared/middleware/validation.middleware.ts around lines 100 to 101, the
params DTO is created without enabling transformation/coercion so route params
like ":id" remain strings; update plainToInstance/validation to enable
transformation/coercion (e.g., pass class-transformer options to convert types
or use validate with { whitelist: true, transform: true, transformOptions: {
enableImplicitConversion: true } }) and optionally enable whitelist to strip
unknown properties; ensure you pass these options to validate() (or use a
combined transform+validate helper) so numeric params are coerced to numbers and
DTO validation behaves correctly.

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