Skip to content

Banners en homepage y su gestión por admins - #165

Merged
leoAchu16 merged 2 commits into
devfrom
OM-523
May 21, 2026
Merged

leoAchu16 merged 2 commits into
devfrom
OM-523

Conversation

@Andoumeda

@Andoumeda Andoumeda commented May 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Notas de la Versión

  • New Features

    • Sistema de gestión de banners: crear, actualizar, listar y activar/desactivar desde el panel de administración.
    • Endpoint público para obtener banners activos con parámetro limit configurable.
  • Documentation

    • Se incorporaron esquemas y tags en la documentación OpenAPI para los endpoints de banners (públicos y admin).
  • Tests

    • Suite de tests unitarios añadida para controladores y servicios de banners (casos de validación y flujo exitoso/error).

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 21, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Se implementa una feature completa de banners: modelo y migración, esquemas OpenAPI, API pública GET /api/banners para banners vigentes, APIs administrativas protegidas (crear/listar/actualizar/toggle) y tests unitarios que cubren validaciones y flujos.

Changes

Banners - Public & Admin APIs

Layer / File(s) Summary
Data contract and schemas
prisma/migrations/..., prisma/schema.prisma, src/docs/schemas/banner.schema.js, src/docs/schemas/admin/admin-banner.schema.js, src/docs/schemas/index.js
Modelo Banners con campos de contenido (title, description, URLs), vigencia (start_at/end_at), control de estado (is_active, status) y auditoría (timestamps). Esquemas OpenAPI para respuestas públicas y admin con paginación.
Public Banner API
src/modules/global/banners/banners.controller.js, src/modules/global/banners/banners.routes.js, src/modules/global/banners/banners.service.js
Endpoint GET /api/banners que expone banners activos y vigentes; servicio filtra por status/is_active y rango de fechas, con validación y límite configurable (default 10, máx 50).
Admin Banner CRUD Services
src/modules/admin/banners/admin-banners.service.js
Servicios de crear (con validaciones de título y rango de fechas), listar (filtros dinámicos: search/active/status + paginación), actualizar parcialmente (preserva campos no proporcionados), y toggle de activación. Incluye utilidades de normalización y mapeo de respuestas.
Admin Banner Controllers and Routes
src/modules/admin/banners/admin-banners.controller.js, src/modules/admin/banners/admin-banners.routes.js
Controladores Express (createAdminBanner, getAdminBanners, updateAdminBanner, toggleAdminBannerActive) con validación de ID y manejo de errores; rutas protegidas con JWT + rol ADMIN, con documentación Swagger y middleware de paginación.
API Integration and Swagger Config
src/app.js, src/config/swagger.config.js, src/modules/admin/index.js
Registro de rutas /api/banners y /api/admin/banners en Express; inclusión de schemas banner en Swagger; tag OpenAPI para agrupar endpoints; re-exports de rutas en módulo admin.
Unit Tests for Banner Services and Controllers
tests/unit/admin/admin-banners.test.js, tests/unit/admin/admin-controllers.test.js, tests/unit/banners/banners.controller.test.js, tests/unit/banners/banners.service.test.js
Tests Vitest para servicios (validaciones de campos, rangos de fechas, búsqueda insensible, paginación) y controladores (respuestas exitosas y manejo de errores); mockeos de Prisma y servicios.

Sequence Diagrams

sequenceDiagram
  participant Client
  participant BannersController
  participant BannersService
  participant Prisma

  Client->>BannersController: GET /api/banners?limit=10
  BannersController->>BannersService: { limit: 10 }
  BannersService->>Prisma: findMany(where: status:true, is_active:true, fechas vigentes, take:10)
  Prisma-->>BannersService: [banners]
  BannersService->>BannersController: banners mapeados
  BannersController-->>Client: 200 JSON
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • CrisNAC
  • SebaKisser
  • DavidVillarM

Poem

🐰 Un conejo encontró código en flor,
banners que brillan con tiempo y color,
admin vigila puertas con rol y razón,
público pasea contento en la función,
saltos de alegría: ¡merge con amor! 🎉

🚥 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 El título describe claramente el cambio principal: agregar banners en la homepage y su gestión por administradores, lo cual coincide con el contenido del PR.
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.

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

✨ 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 OM-523

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.

🔧 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 skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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 and usage tips.

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

🧹 Nitpick comments (6)
src/modules/global/banners/banners.routes.js (1)

13-17: ⚡ Quick win

Documentar límites de limit para alinear contrato y validación real.

El schema del parámetro debería exponer minimum, maximum y default para reflejar el comportamiento del servicio y evitar integraciones inválidas.

Propuesta
  *       - in: query
  *         name: limit
  *         schema:
  *           type: integer
+ *           minimum: 1
+ *           maximum: 50
+ *           default: 10
🤖 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 `@src/modules/global/banners/banners.routes.js` around lines 13 - 17,
Actualizar el esquema OpenAPI del parámetro query "limit" en banners.routes.js
para que refleje la validación real: en el objeto schema de "limit" añadir
minimum (por ejemplo 1), maximum (por ejemplo 100) y default (por ejemplo 20) y
asegurarse de que esos valores coincidan con la lógica del endpoint (p. ej. la
función/handler que procesa el parámetro "limit" o el middleware de validación
usado por la ruta /banners); ajustar los números si la implementación real usa
otros límites para mantener el contrato y la validación sincronizados.
prisma/schema.prisma (1)

64-76: ⚡ Quick win

Agregar @@index en Banners para la consulta de banners activos

El servicio getActiveBannersService consulta status=true, is_active=true, start_at <= now y (end_at IS NULL OR end_at >= now), y ordena por start_at desc y id_banner desc. El modelo Banners no declara @@index, por lo que conviene indexar estos campos.

Propuesta
 model Banners {
@@
   updated_at  DateTime `@updatedAt` `@db.Timestamptz`
+
+  @@index([status, is_active, start_at])
+  @@index([end_at])
 }
🤖 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 `@prisma/schema.prisma` around lines 64 - 76, The Banners model lacks a
compound index for the query used by getActiveBannersService; add an @@index on
the fields used for filtering and ordering to speed that query. Edit the model
Banners and add a compound index referencing status, is_active, start_at and
id_banner (and also include end_at or add a separate @@index on end_at) so the
database can optimize the WHERE (status, is_active, start_at, end_at) and ORDER
BY (start_at desc, id_banner desc) used by getActiveBannersService; target the
model name Banners and the fields status, is_active, start_at, end_at, id_banner
when adding the index declarations.
tests/unit/admin/admin-banners.test.js (1)

121-154: ⚡ Quick win

Sumá casos de éxito para updateAdminBannerService y toggleAdminBannerActiveService.

Acá sólo se validan errores; falta cubrir el flujo exitoso (incluyendo llamada a prisma.banners.update y shape de respuesta), que es clave para el contrato del CRUD admin.

Propuesta de tests adicionales
 describe("updateAdminBannerService", () => {
   beforeEach(() => vi.clearAllMocks());
+  it("actualiza el banner y devuelve el DTO esperado", async () => {
+    prisma.banners.findUnique.mockResolvedValue(mockBanner);
+    prisma.banners.update.mockResolvedValue({ ...mockBanner, title: "Nuevo título" });
+
+    const result = await updateAdminBannerService(1, { title: "  Nuevo título  " });
+
+    expect(prisma.banners.update).toHaveBeenCalled();
+    expect(result.title).toBe("Nuevo título");
+  });
 });
 
 describe("toggleAdminBannerActiveService", () => {
   beforeEach(() => vi.clearAllMocks());
+  it("actualiza isActive cuando el banner existe", async () => {
+    prisma.banners.findUnique.mockResolvedValue(mockBanner);
+    prisma.banners.update.mockResolvedValue({ ...mockBanner, is_active: false });
+
+    const result = await toggleAdminBannerActiveService(1, false);
+
+    expect(prisma.banners.update).toHaveBeenCalledWith(
+      expect.objectContaining({ data: expect.objectContaining({ is_active: false }) })
+    );
+    expect(result.isActive).toBe(false);
+  });
 });
🤖 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 `@tests/unit/admin/admin-banners.test.js` around lines 121 - 154, Add
success-case unit tests for updateAdminBannerService and
toggleAdminBannerActiveService: mock prisma.banners.findUnique to return an
existing banner (e.g., mockBanner), mock prisma.banners.update to return the
updated banner (e.g., mockUpdatedBanner), call updateAdminBannerService(id,
payload) and assert it resolves with the expected shape/fields and that
prisma.banners.update was called with the correct args; do the same for
toggleAdminBannerActiveService(id, isActive) asserting the update call toggles
isActive and the returned object shape matches the contract. Ensure tests
reference updateAdminBannerService, toggleAdminBannerActiveService,
prisma.banners.findUnique and prisma.banners.update, and include assertions for
both the update call parameters and the resolved response shape.
tests/unit/admin/admin-controllers.test.js (1)

263-297: ⚡ Quick win

Falta cubrir el caso de id inválido en controladores de update/toggle.

Conviene agregar tests donde req.params.id no sea numérico para verificar respuesta 400 y que el servicio no se invoque. Eso protege el contrato de validación del controller.

🤖 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 `@tests/unit/admin/admin-controllers.test.js` around lines 263 - 297, Agregar
pruebas para el caso de id inválido en los controladores updateAdminBanner y
toggleAdminBannerActive: crear nuevos it blocks donde req.params.id sea no
numérico (por ejemplo "abc") usando makeCtx, llamar a updateAdminBanner y
toggleAdminBannerActive respectivamente, y verificar que res.status haya sido
llamado con 400 y que updateAdminBannerService / toggleAdminBannerActiveService
no hayan sido invocados; también asegurarse de que next no sea llamado para
mantener el contrato de validación del controller.
tests/unit/banners/banners.controller.test.js (1)

23-35: ⚡ Quick win

Endurecé el contrato de interacción del controller con el servicio.

Además del status, agregá assert de getActiveBannersService con req.query; y en error, verificá next(err) con la misma instancia. Hoy el test deja pasar wiring incorrecto.

🤖 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 `@tests/unit/banners/banners.controller.test.js` around lines 23 - 35, El test
debe verificar que el controller invoque correctamente el servicio con los
parámetros de consulta y que propague exactamente la misma instancia de error a
next; en el caso exitoso, añade un
expect(getActiveBannersService).toHaveBeenCalledWith(req.query) (o con el objeto
creado por makeCtx) después de llamar a getActiveBanners, y en el caso de fallo
crea una instancia de Error (e.g. const err = new Error("fail")), usa
getActiveBannersService.mockRejectedValue(err) y verifica
expect(next).toHaveBeenCalledWith(err) para asegurar que next recibe la misma
instancia; las funciones relevantes son getActiveBanners y
getActiveBannersService.
tests/unit/banners/banners.service.test.js (1)

26-33: ⚡ Quick win

El assert del filtro temporal es demasiado laxo.

start_at: expect.any(Object) y OR: expect.any(Array) no garantizan que el rango de vigencia esté bien construido. Usá tiempo fijo (vi.useFakeTimers) y arrayContaining/objectContaining para validar condiciones exactas.

Ejemplo de assert más específico
+vi.useFakeTimers();
+vi.setSystemTime(new Date("2026-05-21T00:00:00Z"));
 await getActiveBannersService({ limit: 5 });
 
 expect(prisma.banners.findMany).toHaveBeenCalledWith(
   expect.objectContaining({
     where: expect.objectContaining({
       status: true,
       is_active: true,
-      start_at: expect.any(Object),
-      OR: expect.any(Array),
+      start_at: expect.objectContaining({ lte: new Date("2026-05-21T00:00:00Z") }),
+      OR: expect.arrayContaining([
+        { end_at: null },
+        { end_at: expect.objectContaining({ gte: new Date("2026-05-21T00:00:00Z") }) },
+      ]),
     }),
     take: 5,
   })
 );
🤖 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 `@tests/unit/banners/banners.service.test.js` around lines 26 - 33, El test usa
assertions demasiado vagas para el filtro temporal; en la prueba que llama a
prisma.banners.findMany fija el tiempo con vi.useFakeTimers (o
jest.useFakeTimers) y buildea una fecha fija, luego reemplazá expect.any(Object)
para start_at por expect.objectContaining({...}) comprobando las claves exactas
usadas (por ejemplo gte/lte con las fechas fijas) y reemplazá OR:
expect.any(Array) por expect.objectContaining({ OR:
expect.arrayContaining([expect.objectContaining({...}),
expect.objectContaining({...})]) }) para validar cada rama del OR de forma
explícita; mantiene prisma.banners.findMany como punto de verificación y usa
expect.objectContaining/expect.arrayContaining para comparar solo las partes
relevantes del filtro.
🤖 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 `@prisma/migrations/20260521004958_add_banners/migration.sql`:
- Around line 2-16: Agregar una constraint CHECK en la definición de la tabla
Banners para evitar que end_at sea anterior a start_at: en la CREATE TABLE
"Banners" (o con un ALTER TABLE posterior) añade una constraint con un nombre
único, por ejemplo Banners_valid_period_chk, que exija (end_at IS NULL OR end_at
>= start_at) sobre las columnas "start_at" y "end_at" para garantizar vigencia
temporal válida.

In `@src/modules/admin/banners/admin-banners.service.js`:
- Around line 160-167: Hay una TOCTOU: el código hace un
prisma.banners.findUnique y comprueba existing.status antes de un update,
permitiendo que otro request cambie status entre ambas operaciones; cambia la
lógica para hacer la actualización condicional/atómica usando
prisma.banners.update (o updateMany) con un where que incluya tanto id_banner:
id como status: true, y luego verifica el resultado (fila afectada o valor
retornado) para lanzar NotFoundError si no se actualizó nada; aplícalo también
en las otras dos ubicaciones que usan el mismo patrón (las llamadas a
prisma.banners.findUnique / update alrededor de bannerSelect en este archivo).
- Around line 133-153: The pagination logic can produce Infinity when limit is
0; before using limit in prisma.banners.findMany (take) and computing
totalPages, normalize it to a safe minimum (e.g., const safeLimit = Math.max(1,
Number(limit) || 1)) and use safeLimit for take and Math.ceil(total /
safeLimit), while still returning the original page/limit inputs if desired;
update references in this block (pagination destructure, prisma.banners.findMany
take, and totalPages calculation) to use the normalized value.

In `@src/modules/global/banners/banners.service.js`:
- Around line 35-37: El guard clause devuelve [] cuando
prisma?.banners?.findMany no existe, ocultando un fallo de
contrato/infraestructura; en src/modules/global/banners/banners.service.js
reemplaza ese return [] por lanzar un error claro (por ejemplo throw new
Error(...)) que incluya contexto sobre el cliente Prisma y la ausencia de
banners.findMany, para que el caller/monitoring detecte el problema; además
actualiza los tests para cubrir el caso donde prisma.banners es undefined y
assertar que se lanza el error.

---

Nitpick comments:
In `@prisma/schema.prisma`:
- Around line 64-76: The Banners model lacks a compound index for the query used
by getActiveBannersService; add an @@index on the fields used for filtering and
ordering to speed that query. Edit the model Banners and add a compound index
referencing status, is_active, start_at and id_banner (and also include end_at
or add a separate @@index on end_at) so the database can optimize the WHERE
(status, is_active, start_at, end_at) and ORDER BY (start_at desc, id_banner
desc) used by getActiveBannersService; target the model name Banners and the
fields status, is_active, start_at, end_at, id_banner when adding the index
declarations.

In `@src/modules/global/banners/banners.routes.js`:
- Around line 13-17: Actualizar el esquema OpenAPI del parámetro query "limit"
en banners.routes.js para que refleje la validación real: en el objeto schema de
"limit" añadir minimum (por ejemplo 1), maximum (por ejemplo 100) y default (por
ejemplo 20) y asegurarse de que esos valores coincidan con la lógica del
endpoint (p. ej. la función/handler que procesa el parámetro "limit" o el
middleware de validación usado por la ruta /banners); ajustar los números si la
implementación real usa otros límites para mantener el contrato y la validación
sincronizados.

In `@tests/unit/admin/admin-banners.test.js`:
- Around line 121-154: Add success-case unit tests for updateAdminBannerService
and toggleAdminBannerActiveService: mock prisma.banners.findUnique to return an
existing banner (e.g., mockBanner), mock prisma.banners.update to return the
updated banner (e.g., mockUpdatedBanner), call updateAdminBannerService(id,
payload) and assert it resolves with the expected shape/fields and that
prisma.banners.update was called with the correct args; do the same for
toggleAdminBannerActiveService(id, isActive) asserting the update call toggles
isActive and the returned object shape matches the contract. Ensure tests
reference updateAdminBannerService, toggleAdminBannerActiveService,
prisma.banners.findUnique and prisma.banners.update, and include assertions for
both the update call parameters and the resolved response shape.

In `@tests/unit/admin/admin-controllers.test.js`:
- Around line 263-297: Agregar pruebas para el caso de id inválido en los
controladores updateAdminBanner y toggleAdminBannerActive: crear nuevos it
blocks donde req.params.id sea no numérico (por ejemplo "abc") usando makeCtx,
llamar a updateAdminBanner y toggleAdminBannerActive respectivamente, y
verificar que res.status haya sido llamado con 400 y que
updateAdminBannerService / toggleAdminBannerActiveService no hayan sido
invocados; también asegurarse de que next no sea llamado para mantener el
contrato de validación del controller.

In `@tests/unit/banners/banners.controller.test.js`:
- Around line 23-35: El test debe verificar que el controller invoque
correctamente el servicio con los parámetros de consulta y que propague
exactamente la misma instancia de error a next; en el caso exitoso, añade un
expect(getActiveBannersService).toHaveBeenCalledWith(req.query) (o con el objeto
creado por makeCtx) después de llamar a getActiveBanners, y en el caso de fallo
crea una instancia de Error (e.g. const err = new Error("fail")), usa
getActiveBannersService.mockRejectedValue(err) y verifica
expect(next).toHaveBeenCalledWith(err) para asegurar que next recibe la misma
instancia; las funciones relevantes son getActiveBanners y
getActiveBannersService.

In `@tests/unit/banners/banners.service.test.js`:
- Around line 26-33: El test usa assertions demasiado vagas para el filtro
temporal; en la prueba que llama a prisma.banners.findMany fija el tiempo con
vi.useFakeTimers (o jest.useFakeTimers) y buildea una fecha fija, luego
reemplazá expect.any(Object) para start_at por expect.objectContaining({...})
comprobando las claves exactas usadas (por ejemplo gte/lte con las fechas fijas)
y reemplazá OR: expect.any(Array) por expect.objectContaining({ OR:
expect.arrayContaining([expect.objectContaining({...}),
expect.objectContaining({...})]) }) para validar cada rama del OR de forma
explícita; mantiene prisma.banners.findMany como punto de verificación y usa
expect.objectContaining/expect.arrayContaining para comparar solo las partes
relevantes del filtro.
🪄 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: CHILL

Plan: Pro

Run ID: 741000eb-ec0a-4427-9128-2a5162ba88c1

📥 Commits

Reviewing files that changed from the base of the PR and between fd96fce and 9a742c2.

📒 Files selected for processing (18)
  • prisma/migrations/20260521004958_add_banners/migration.sql
  • prisma/schema.prisma
  • src/app.js
  • src/config/swagger.config.js
  • src/docs/schemas/admin/admin-banner.schema.js
  • src/docs/schemas/banner.schema.js
  • src/docs/schemas/index.js
  • src/modules/admin/banners/admin-banners.controller.js
  • src/modules/admin/banners/admin-banners.routes.js
  • src/modules/admin/banners/admin-banners.service.js
  • src/modules/admin/index.js
  • src/modules/global/banners/banners.controller.js
  • src/modules/global/banners/banners.routes.js
  • src/modules/global/banners/banners.service.js
  • tests/unit/admin/admin-banners.test.js
  • tests/unit/admin/admin-controllers.test.js
  • tests/unit/banners/banners.controller.test.js
  • tests/unit/banners/banners.service.test.js

Comment thread prisma/migrations/20260521004958_add_banners/migration.sql
Comment thread src/modules/admin/banners/admin-banners.service.js
Comment thread src/modules/admin/banners/admin-banners.service.js
Comment thread src/modules/global/banners/banners.service.js
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
5.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@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.

Caution

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

⚠️ Outside diff range comments (2)
src/modules/admin/banners/admin-banners.service.js (2)

162-188: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

La actualización parcial todavía puede pisar cambios concurrentes.

startAt/endAt se validan contra existing, pero el write sólo condiciona por id_banner y status. Si dos admins editan fechas en paralelo, ambos requests pueden pasar la validación y terminar guardando un rango inválido o sobrescribiendo el cambio del otro. Acá necesitás merge atómico: transacción con lock o optimistic locking con updated_at en el where.

Also applies to: 216-226

🤖 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 `@src/modules/admin/banners/admin-banners.service.js` around lines 162 - 188,
The patch validates startAt/endAt against the fetched existing record but then
updates without protecting against concurrent edits, so change the update to
perform an atomic merge using optimistic locking: include the record's current
timestamp (existing.updated_at) in the update WHERE clause (or run the
read+write inside a DB transaction with a row lock) so the UPDATE on
prisma.banners only succeeds if updated_at matches the value you validated; if
the update affects 0 rows, throw a concurrency error and surface that to the
caller. Apply the same pattern for the other update block referenced around the
216-226 region so both partial-update flows use the updated_at-based WHERE (or
transaction+lock) to prevent lost updates.

133-154: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

safeLimit/safeSkip todavía permiten valores inválidos; normalizalos a enteros finitos

Los guards actuales aceptan 2.5 y Infinity, que terminan en take/skip y también en totalPages. Para Prisma, skip/take representan cantidades/offsets de paginación y se deben usar como enteros (evitar decimales y Infinity).

💡 Ajuste mínimo
   const { page = 1, limit = 20, skip = 0 } = pagination;
-  const safeLimit = Number(limit) > 0 ? Number(limit) : 20;
-  const safeSkip = Number(skip) >= 0 ? Number(skip) : 0;
+  const parsedLimit = Number(limit);
+  const parsedSkip = Number(skip);
+  const safeLimit = Number.isInteger(parsedLimit) && parsedLimit > 0 ? parsedLimit : 20;
+  const safeSkip = Number.isInteger(parsedSkip) && parsedSkip >= 0 ? parsedSkip : 0;
🤖 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 `@src/modules/admin/banners/admin-banners.service.js` around lines 133 - 154,
The current safeLimit/safeSkip allow non-integer and non-finite values (e.g.,
2.5 or Infinity); update the normalization where safeLimit and safeSkip are
computed so they coerce to finite integers: use Number.isFinite on
Number(limit)/Number(skip) and apply Math.floor (or parseInt) then clamp
safeLimit to a minimum of 1 and safeSkip to a minimum of 0; ensure the updated
safeLimit/safeSkip are used in prisma.banners.findMany (take/skip) and in the
totalPages calculation (Math.ceil(total / safeLimit)).
🤖 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.

Outside diff comments:
In `@src/modules/admin/banners/admin-banners.service.js`:
- Around line 162-188: The patch validates startAt/endAt against the fetched
existing record but then updates without protecting against concurrent edits, so
change the update to perform an atomic merge using optimistic locking: include
the record's current timestamp (existing.updated_at) in the update WHERE clause
(or run the read+write inside a DB transaction with a row lock) so the UPDATE on
prisma.banners only succeeds if updated_at matches the value you validated; if
the update affects 0 rows, throw a concurrency error and surface that to the
caller. Apply the same pattern for the other update block referenced around the
216-226 region so both partial-update flows use the updated_at-based WHERE (or
transaction+lock) to prevent lost updates.
- Around line 133-154: The current safeLimit/safeSkip allow non-integer and
non-finite values (e.g., 2.5 or Infinity); update the normalization where
safeLimit and safeSkip are computed so they coerce to finite integers: use
Number.isFinite on Number(limit)/Number(skip) and apply Math.floor (or parseInt)
then clamp safeLimit to a minimum of 1 and safeSkip to a minimum of 0; ensure
the updated safeLimit/safeSkip are used in prisma.banners.findMany (take/skip)
and in the totalPages calculation (Math.ceil(total / safeLimit)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cae52487-fbd0-4350-aa64-e23a238499af

📥 Commits

Reviewing files that changed from the base of the PR and between 9a742c2 and d063949.

📒 Files selected for processing (2)
  • src/modules/admin/banners/admin-banners.service.js
  • src/modules/global/banners/banners.service.js

@leoAchu16
leoAchu16 merged commit 1797dfd into dev May 21, 2026
3 of 4 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request May 22, 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