Skip to content

feat(OM-540): CRUD admin de etiquetas (ProductTags) - Nuevo modulo ad… - #178

Merged
leoAchu16 merged 2 commits into
devfrom
OM-540
May 30, 2026
Merged

leoAchu16 merged 2 commits into
devfrom
OM-540

Conversation

@Lianyang1234

@Lianyang1234 Lianyang1234 commented May 29, 2026 •

Copy link
Copy Markdown
Collaborator

…min/tags con service, controller, routes (Swagger) - Endpoints POST/GET/PATCH/DELETE /api/admin/tags - Tests unitarios (37 tests) - Registro en admin/index.js y app.js

Summary by CodeRabbit

  • New Features

    • Nuevo módulo de administración de etiquetas con endpoints CRUD (crear, listar, actualizar, eliminar), protección por autenticación y rol administrador.
    • Listados devuelven conteo de productos por etiqueta y sólo muestran etiquetas activas; eliminación marca la etiqueta como inactiva.
  • Validaciones

    • Nombres se normalizan y validan (trim, máximo 20 caracteres) y se evita duplicado activo; conflictos devuelven errores adecuados.
  • Tests

    • Cobertura unitaria completa para todos los endpoints administrativos de etiquetas.

Review Change Stack

…min/tags con service, controller, routes (Swagger) - Endpoints POST/GET/PATCH/DELETE /api/admin/tags - Tests unitarios (37 tests) - Registro en admin/index.js y app.js
@coderabbitai

coderabbitai Bot commented May 29, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 01b95cc9-462b-4fc2-b73b-88457141e156

📥 Commits

Reviewing files that changed from the base of the PR and between 62f1b6a and 1a3be76.

📒 Files selected for processing (3)
  • prisma/schema.prisma
  • src/modules/admin/tags/admin-tag.service.js
  • tests/unit/admin/admin-tags.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/modules/admin/tags/admin-tag.service.js
  • tests/unit/admin/admin-tags.test.js

📝 Walkthrough

Walkthrough

Se implementa una API CRUD administrativa para etiquetas en /api/admin/tags: validación/normalización de nombres, servicios CRUD con lógica de duplicados y transacciones, controladores HTTP, rutas protegidas exportadas/montadas en app, cambio en esquema Prisma y tests unitarios completos.

Changes

Administración de Etiquetas de Producto

Layer / File(s) Summary
Validación y normalización de datos
src/modules/admin/tags/admin-tag.service.js
Constante MAX_TAG_NAME_LENGTH y normalizeAdminTagName(name) que valida tipo, trim y longitud, lanzando ValidationError cuando aplica.
Servicios CRUD
src/modules/admin/tags/admin-tag.service.js
createAdminTagService, getAllAdminTagsService, updateAdminTagService, deleteAdminTagService con búsqueda de duplicados case-insensitive, mapeo de _count.product_tag_relations a productCount, manejo de P2002 -> ConflictError, y uso de transacción para eliminar relaciones y desactivar tags.
Controladores HTTP
src/modules/admin/tags/admin-tag.controller.js
Handlers async createAdminTag, getAdminTags, updateAdminTag, deleteAdminTag que delegan a servicios, validan id en mutaciones, responden con 201/200/204 y propagan errores con next(error).
Rutas y montaje
src/modules/admin/tags/admin-tag.routes.js, src/modules/admin/index.js, src/app.js
Router Express con POST/GET/PATCH/DELETE protegido por authenticate + requireRole(ROLES.ADMIN), Swagger inline; exporta adminTagRoutes y lo monta en app.use("/api/admin/tags", adminTagRoutes).
Prisma schema
prisma/schema.prisma
Agrega @@unique([name], where: { status: true }) en ProductTags para unicidad sólo en filas activas.
Tests unitarios
tests/unit/admin/admin-tags.test.js
Suite extensa con mocks de Prisma y middleware JWT de prueba; cubre 401/403, validaciones de name y id, 404/409, éxitos y verificaciones de queries y llamadas a $transaction/deleteMany/update.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Express
  participant Controller
  participant TagService
  participant Prisma
  Client->>Express: POST /api/admin/tags {name}
  Express->>Express: authenticate, requireRole(ROLES.ADMIN)
  Express->>Controller: createAdminTag(req)
  Controller->>TagService: createAdminTagService(name)
  TagService->>Prisma: findFirst (duplicado case-insensitive)
  Prisma-->>TagService: resultado
  TagService->>Prisma: create productTags
  Prisma-->>TagService: tag creada
  TagService-->>Controller: DTO {id,name,status,createdAt}
  Controller-->>Client: 201 JSON
  Client->>Express: GET /api/admin/tags
  Express->>Express: authenticate, requireRole(ROLES.ADMIN)
  Express->>Controller: getAdminTags(req)
  Controller->>TagService: getAllAdminTagsService()
  TagService->>Prisma: findMany (status=true, orderBy name, select _count)
  Prisma-->>TagService: tags con _count
  TagService-->>Controller: array mapeado con productCount
  Controller-->>Client: 200 JSON
  Client->>Express: PATCH /api/admin/tags/:id {name}
  Express->>Express: authenticate, requireRole(ROLES.ADMIN)
  Express->>Controller: updateAdminTag(req)
  Controller->>TagService: updateAdminTagService(id,name)
  TagService->>Prisma: findUnique (validar existe)
  Prisma-->>TagService: tag
  TagService->>Prisma: findFirst (duplicado excluyendo id)
  Prisma-->>TagService: resultado
  TagService->>Prisma: update productTags
  Prisma-->>TagService: tag actualizado
  TagService-->>Controller: DTO actualizado
  Controller-->>Client: 200 JSON
  Client->>Express: DELETE /api/admin/tags/:id
  Express->>Express: authenticate, requireRole(ROLES.ADMIN)
  Express->>Controller: deleteAdminTag(req)
  Controller->>TagService: deleteAdminTagService(id)
  TagService->>Prisma: $transaction
  Prisma->>Prisma: deleteMany productTagRelations
  Prisma->>Prisma: update productTags (status=false)
  Prisma-->>TagService: completado
  TagService-->>Controller: -
  Controller-->>Client: 204 No Content
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • CrisNAC/BackendMarketplace#100: Modificaciones en composición/ruteo del módulo admin (exports y montajes), similar en área de módulos administrativos.

Suggested reviewers

  • CrisNAC
  • DavidVillarM

Poem

🐰 Brinco entre etiquetas y rutas con afán,
normalizo nombres, evito duplicar,
creo, listo, actualizo y borro con paz,
transacciones seguras que cuidan el lugar,
¡mi API de tags lista para saltar!

🚥 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 adecuadamente el cambio principal: implementación de un nuevo módulo CRUD para administración de etiquetas de producto (ProductTags), que es el objetivo central 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-540

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: 1

🧹 Nitpick comments (1)
tests/unit/admin/admin-tags.test.js (1)

212-233: 💤 Low value

Test redundante con el caso 201 previo.

El test "trim del nombre al crear" (212-233) verifica esencialmente lo mismo que "devuelve 201 y crea etiqueta con status=true" (179-210), que ya asserta el trim del nombre en prisma.productTags.create. Podés consolidarlos o aprovechar este test para cubrir un caso aún no contemplado (p. ej. duplicado de nombre soft-deleted). Solo una sugerencia menor.

🤖 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-tags.test.js` around lines 212 - 233, El test "trim
del nombre al crear" duplica la verificación ya cubierta por "devuelve 201 y
crea etiqueta con status=true"; elimina este test redundante OR repurposalo para
cubrir un caso no probado: simular que prisma.productTags.findFirst devuelve un
registro con el mismo name y status: false (soft-deleted) and then assert el
comportamiento esperado (por ejemplo, que la API re-cree/active la etiqueta o
responda con conflicto) usando los mismos helpers (asRole,
request(app).post("/api/admin/tags")) y las mismas mocks
(prisma.productTags.findFirst / prisma.productTags.create o
prisma.productTags.update) para reflejar la lógica que quieras validar.
🤖 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 `@src/modules/admin/tags/admin-tag.service.js`:
- Around line 22-48: The current TOCTOU in admin-tag.service.js
(prisma.productTags.findFirst + prisma.productTags.create using normalizedName)
can create duplicate active tags because the DB lacks a unique constraint;
update the Prisma schema for model ProductTags to add a DB-level unique
constraint/index enforcing uniqueness for active names (e.g. a
partial/functional unique index on lower(name) where status = true or an
equivalent DB constraint) and run the migration, and update the service to catch
Prisma unique-constraint errors (P2002) thrown by prisma.productTags.create and
translate them into a consistent 409 response or ValidationError indicating the
tag already exists; reference prisma.productTags.findFirst,
prisma.productTags.create, normalizedName, and the ProductTags model when
locating code to change.

---

Nitpick comments:
In `@tests/unit/admin/admin-tags.test.js`:
- Around line 212-233: El test "trim del nombre al crear" duplica la
verificación ya cubierta por "devuelve 201 y crea etiqueta con status=true";
elimina este test redundante OR repurposalo para cubrir un caso no probado:
simular que prisma.productTags.findFirst devuelve un registro con el mismo name
y status: false (soft-deleted) and then assert el comportamiento esperado (por
ejemplo, que la API re-cree/active la etiqueta o responda con conflicto) usando
los mismos helpers (asRole, request(app).post("/api/admin/tags")) y las mismas
mocks (prisma.productTags.findFirst / prisma.productTags.create o
prisma.productTags.update) para reflejar la lógica que quieras validar.
🪄 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: fdaa3af1-4d9b-402d-8f6d-075f09a6ce76

📥 Commits

Reviewing files that changed from the base of the PR and between a6355f9 and 62f1b6a.

📒 Files selected for processing (6)
  • src/app.js
  • src/modules/admin/index.js
  • src/modules/admin/tags/admin-tag.controller.js
  • src/modules/admin/tags/admin-tag.routes.js
  • src/modules/admin/tags/admin-tag.service.js
  • tests/unit/admin/admin-tags.test.js

Comment on lines +22 to +48
const existingTag = await prisma.productTags.findFirst({
where: {
name: {
equals: normalizedName,
mode: "insensitive"
},
status: true
},
select: { id_product_tag: true }
});

if (existingTag) {
throw new ValidationError("Ya existe una etiqueta con ese nombre");
}

const createdTag = await prisma.productTags.create({
data: {
name: normalizedName,
status: true
},
select: {
id_product_tag: true,
name: true,
status: true,
created_at: true
}
});

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Buscar el modelo de etiquetas y restricciones en el schema de Prisma
fd -e prisma | xargs -I {} sh -c 'echo "== {} =="; cat "{}"' | rg -n -i -A20 'model\s+ProductTags|product_tag|@@unique|`@unique`'

Repository: CrisNAC/BackendMarketplace

Length of output: 14068


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Localizar schemas Prisma
SCHEMAS=$(fd -e prisma . | tr '\n' ' ')
echo "Schemas:"; echo "$SCHEMAS"

# Mostrar el bloque completo del modelo ProductTags
for f in $(fd -e prisma .); do
  echo "===== $f (ProductTags) ====="
  rg -n -S "model\s+ProductTags\s*\{" "$f" -n || true
  # Capturar ~60 líneas desde la definición para incluir @@unique/@@index si existiera
  rg -n -S "model\s+ProductTags\s*\{" "$f" -n | awk -F: '{print $1 ":" $2}' >/dev/null || true
  # Mejor: usar un rango fijo desde la primera aparición
  line=$(rg -n -S "model\s+ProductTags\s*\{" "$f" | head -n1 | cut -d: -f1 || true)
  if [ -n "${line:-}" ]; then
    start=$((line))
    end=$((line+80))
    sed -n "${start},${end}p" "$f"
  fi
done | head -n 200

Repository: CrisNAC/BackendMarketplace

Length of output: 3761


🏁 Script executed:

#!/bin/bash
set -euo pipefail

SCHEMA_FILES=$(fd -e prisma .)
echo "Prisma files:"
echo "$SCHEMA_FILES"

# 1) Mostrar el bloque completo de ProductTags (por rango de líneas)
echo "----- ProductTags block (context) -----"
for f in $SCHEMA_FILES; do
  line=$(rg -n -S "model\s+ProductTags\s*\{" "$f" | head -n1 | cut -d: -f1 || true)
  if [ -n "${line:-}" ]; then
    start=$((line-5))
    end=$((line+60))
    echo "==== $f lines $start-$end ===="
    sed -n "${start},${end}p" "$f"
  fi
done

# 2) Buscar restricciones únicas o índices relacionados con "ProductTags" o "name"
echo "----- Search unique/index patterns -----"
rg -n -S "ProductTags|@@unique|`@unique`|@@index|`@index`" $SCHEMA_FILES | rg -n -S "ProductTags|name|productTags|product_tag" || true

# 3) Buscar explícitamente si "name" tiene `@unique` dentro de ProductTags
echo "----- Search `@unique` with name -----"
rg -n -S "`@unique`" $SCHEMA_FILES | rg -n -S "name|ProductTags" || true

Repository: CrisNAC/BackendMarketplace

Length of output: 3038


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Buscar migraciones que toquen product_tags / producttags / name con unique
if [ -d prisma/migrations ]; then
  echo "== scanning prisma/migrations =="
  rg -n -i --hidden \
    "(product[_-]tags|producttags|ProductTags)|unique|constraint|index|lower\\(.*name\\)|citext|collate" \
    prisma/migrations \
    || true
else
  echo "No existe prisma/migrations en este repo."
fi

Repository: CrisNAC/BackendMarketplace

Length of output: 24810


Posible duplicación por falta de restricción única (y condición de carrera) en ProductTags

En el esquema actual (model ProductTags) no hay @unique/@@unique sobre name, así que hoy no hay un índice único que dispare P2002; igual, el patrón findFirst(...status: true) + create(...) tiene una ventana TOCTOU y permite crear dos etiquetas activas con el mismo normalizedName ante requests concurrentes. También puede terminar habiendo varias filas con el mismo name si existe una etiqueta soft-deleted (status=false) y llegan dos altas en paralelo.

Conviene imponer unicidad a nivel DB para el caso “activo” (p.ej. índice único parcial por lower(name) donde status=true) y, si se aplica esa restricción, capturar el error de conflicto para devolver un 409/400 consistente.

🤖 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/tags/admin-tag.service.js` around lines 22 - 48, The
current TOCTOU in admin-tag.service.js (prisma.productTags.findFirst +
prisma.productTags.create using normalizedName) can create duplicate active tags
because the DB lacks a unique constraint; update the Prisma schema for model
ProductTags to add a DB-level unique constraint/index enforcing uniqueness for
active names (e.g. a partial/functional unique index on lower(name) where status
= true or an equivalent DB constraint) and run the migration, and update the
service to catch Prisma unique-constraint errors (P2002) thrown by
prisma.productTags.create and translate them into a consistent 409 response or
ValidationError indicating the tag already exists; reference
prisma.productTags.findFirst, prisma.productTags.create, normalizedName, and the
ProductTags model when locating code to change.

@leoAchu16

Copy link
Copy Markdown
Collaborator

revisar sugerencias del conejo

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

@leoAchu16
leoAchu16 merged commit 9fc2310 into dev May 30, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants