Skip to main content

gio-api-notifications

1. Descripción general

Según el pom.xml, el proyecto se describe como "Project for gio api notifications". Es una API REST cuya única responsabilidad es recibir payloads JSON de actualización/creación de cliente enviados por los sistemas GIO y POS, validarlos como JSON bien formado, y persistirlos como ficheros .json con marca de tiempo en un bucket de Google Cloud Storage, para que procesos posteriores los recojan de forma asíncrona. No realiza ninguna validación de negocio sobre el contenido del payload más allá de comprobar que es JSON válido.

2. Información técnica

CampoValor
artifactIdgio-api-notifications
groupIdcom.hawkersco
version1.0.25
Java25 (maven.compiler.release=25)
Spring Boot4.0.6
Tipo de artefactojar (ejecutable, API web de larga duración)
MódulosNo aplica (proyecto de módulo único)

3. Arquitectura y diseño

Es una API REST clásica con seguridad basada en Basic Auth y roles. Paquetes principales:

  • com.hawkersco.gioapinotifications — clase principal (GioApiNotificationsApplication, con @EnableScheduling, ver hallazgo en sección 13).
  • .controllerGioApiNotificationsController, único controlador de la aplicación.
  • .configGioApiNotificationsConfig (@Order(1), usuarios en memoria) y App2ConfigurationAdapter (@Order(2), SecurityFilterChain, CORS, exclusión de Swagger).
  • .exceptionGcsUploadException (excepción de dominio) y GlobalExceptionHandler (@RestControllerAdvice).
  • utils.GioApiNotificationsConstfuera del paquete raíz com.hawkersco (paquete utils a secas); inconsistencia señalada explícitamente como intencional en el CLAUDE.md del proyecto.
flowchart TD
A[GIO] -->|POST /api/gio-notification/update-client<br/>rol GIOXHAWKERS| C[GioApiNotificationsController]
B[POS] -->|POST /api/pos-notification/update-client<br/>rol ADMIN| C
C -->|valida JSON| C
C -->|sube fichero| D[(GCS bucket hawkers-gio)]
C -.->|fallo de subida| E[GcsUploadException]
E --> F[GlobalExceptionHandler]
F -->|notifica| G[Slack]

Dos @Configuration de seguridad ordenadas explícitamente (@Order(1)/@Order(2)): GioApiNotificationsConfig define los usuarios en memoria (admin con rol ADMIN, gioxhawkers con rol GIOXHAWKERS, ambos con la misma contraseña, proveniente de gioapinotifications.encodepass), y App2ConfigurationAdapter define el SecurityFilterChain, la política CORS y qué rutas requieren qué rol.

4. Dependencias principales

DependenciaPropósito
spring-boot-starter-webExposición de los endpoints REST
spring-boot-starter-securityAutenticación HTTP Basic + autorización por roles
org.json:json:20240303Validación de que el payload recibido es JSON bien formado
lombokGeneración de código boilerplate
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOTDateUtils (timestamp del nombre de fichero), StorageUtils (subida a GCS)
com.hawkersco:slack-client:1.0.25-SNAPSHOTSlackClient/MessageSlack para notificar fallos de subida
spring-boot-starter-test (test)JUnit 5 + Spring Test

5. API / Endpoints

MétodoRutaDescripciónAutenticación / Rol
GET/api/checkHealth check públicoNinguna (permitAll)
POST/api/gio-notification/update-clientRecibe una actualización/creación de cliente desde GIO y la guarda en GCSHTTP Basic, rol GIOXHAWKERS
POST/api/pos-notification/update-clientRecibe una actualización/creación de cliente desde POS y la guarda en GCSHTTP Basic, rol ADMIN

Ambos endpoints de actualización devuelven siempre HTTP 200, incluso ante fallos: si el cuerpo no es JSON válido, responden "true"; si la subida a GCS falla, el GlobalExceptionHandler también responde "true" tras notificar el error a Slack (diseño deliberado para no propagar errores al llamador, según el javadoc del propio controlador).

Ejemplo de respuesta exitosa:

{"Successfully saved": "update_client_gio_20260710153000123.json"}

6. Integraciones externas

SistemaProtocoloDirecciónDetalle
Google Cloud Storage (bucket hawkers-gio)API de GCS (StorageUtils)SalientePersistencia de los payloads recibidos como ficheros .json, bajo <appName>/update-client-gio-pending/ o <appName>/update-client-pos-pending/
SlackHTTP (SlackClient)SalienteNotificación cuando falla la subida a GCS

7. Configuración

Este proyecto es una excepción positiva dentro del ecosistema: no existe un fichero application.properties local con valores hardcodeados; solo hay application-pro.properties, y todos sus valores sensibles ya usan ${VARIABLE}. No se ha encontrado ninguna credencial real en el repositorio.

ClaveDescripciónEjemplo
server.portPuerto HTTP del servicio80
server.session.tracking-modesModo de seguimiento de sesióncookie
gioapinotifications.encodepassContraseña (en claro antes de cifrar con BCrypt) compartida por los usuarios admin y gioxhawkers${encodePass}
gcs.bucket.nameBucket de GCS destinohawkers-gio
slack.client.urlURL API Slack${slackClientUrl}
slack.auth.tokenToken bot de Slack${slackAuthToken}
slack.channel.idCanal de notificaciones${slackChannelId}

Las credenciales de GCS se resuelven vía Application Default Credentials (GOOGLE_APPLICATION_CREDENTIALS), montadas en el Deployment desde el Secret pi-saldum-gcp-credentials en /etc/gcp/sa_credentials.json.

No se emite alerta de seguridad por credenciales hardcodeadas en este proyecto, ya que no existen en el repositorio; ver en la sección 13 una observación sobre el diseño de la autenticación (contraseña compartida entre roles) y la política CORS.

8. Persistencia

No aplica a este proyecto. No usa base de datos: el estado se materializa como ficheros JSON en el bucket de GCS.

9. Procesos programados y mensajería

No hay ningún job real programado ni listener de colas en este servicio: es puramente reactivo a las peticiones HTTP entrantes. Ver hallazgo en la sección 13 sobre @EnableScheduling declarada sin ningún @Scheduled asociado.

10. Ejecución en local

Requisitos previos: JDK 25, Maven, credenciales de aplicación por defecto de Google con permiso de escritura sobre el bucket hawkers-gio, y variables de entorno resueltas para encodePass, slackClientUrl, slackAuthToken, slackChannelId (no hay perfil local con valores por defecto).

# Compilar sin tests
./mvnw -B -DskipTests clean install

# Compilar con tests
./mvnw clean install

# Ejecutar tests
./mvnw test

# Ejecutar un test concreto
./mvnw test -Dtest=GioApiNotificationsApplicationTests

# Ejecutar la aplicación localmente
./mvnw spring-boot:run

Verificación de que el servicio está operativo: GET /api/check (público, sin autenticación) devuelve true con 200 OK. No hay Actuator configurado.

11. Despliegue

  • Imagen: construida con jib-maven-plugin (base eclipse-temurin:25-jre, containerizingMode=packaged), publicada en europe-west3-docker.pkg.dev/pi-saldum/pi-repo/gio-api-notifications:<tag>.
  • Orquestación: Kubernetes Deployment (k8s/deployment.yaml, no CronJob) en el clúster GKE pi-cluster-hw (zona europe-west3-a, proyecto pi-saldum), namespace pi, replicas: 1, restartPolicy: Always. Sin livenessProbe/readinessProbe configurados, pese a existir el endpoint /api/check que podría usarse para ello.
  • CI/CD (Jenkins): pipeline con 2 etapas — CheckoutBuild & Push (renombra application-pro.properties a application.properties antes de mvn clean package jib:build) → Deploy to GKE (aplica el manifiesto templado vía sed y espera el rollout status del Deployment).
  • Las variables sensibles se inyectan en el pod mediante un Secret de Kubernetes llamado igual que la app (gio-api-notifications).

Job de Jenkins: https://jenkins-pi.hawkersco.net/job/gio-api-notifications/

12. Manejo de errores y logging

GlobalExceptionHandler (@RestControllerAdvice) intercepta GcsUploadException (lanzada por el controlador cuando falla la subida a GCS), registra el error con Logger.severe, envía una notificación a Slack con el mensaje preformado según el origen (GIO o POS), y devuelve 200 OK con "true" para no propagar el fallo al llamador. Los payloads que no son JSON válido también devuelven 200 OK con "true" sin lanzar excepción ni registrar el error como fallo (solo se registra el payload recibido a nivel INFO). No hay otro manejo de errores adicional (p. ej. para fallos de autenticación, gestionados por defecto por Spring Security con 401).

13. Notas y consideraciones

  • @EnableScheduling sin ningún @Scheduled: la clase principal habilita la programación de tareas de Spring, pero no existe ningún método @Scheduled en todo el código fuente del proyecto. Es probable que sea un vestigio de una versión anterior o una plantilla compartida; no tiene efecto funcional pero añade una dependencia/inicialización innecesaria.
  • Contraseña compartida entre roles distintos: GioApiNotificationsConfig crea los usuarios admin (rol ADMIN) y gioxhawkers (rol GIOXHAWKERS) con la misma contraseña (gioapinotifications.encodepass). Cualquier consumidor con esa contraseña puede autenticarse indistintamente como cualquiera de los dos usuarios y, dado que solo se autoriza por rol y no por usuario, cualquiera con la contraseña podría, en principio, invocar ambos endpoints si conociera también el nombre de usuario del otro rol. Sería más seguro usar contraseñas independientes por integración.
  • Política CORS permisiva a nivel global: App2ConfigurationAdapter.corsConfigurationSource() registra applyPermitDefaultValues() para el patrón /**, lo que habilita CORS con orígenes por defecto (*) para todas las rutas de la aplicación, incluidos los endpoints autenticados. Combinado con HTTP Basic Auth, esto no expone credenciales por sí solo, pero amplía la superficie de peticiones cross-origin más de lo que estrictamente necesitan los dos endpoints de notificación.
  • SlackClientConfig mencionada en CLAUDE.md no existe en este repositorio: el CLAUDE.md describe la configuración del cliente Slack como gestionada por una clase SlackClientConfig, pero no hay ninguna clase con ese nombre en el código fuente actual; el bean SlackClient se resuelve mediante la autoconfiguración interna de la librería slack-client (patrón @AutoConfiguration + spring.factories), no por una clase local. Puede ser una referencia heredada de otro proyecto de la familia.
  • Inconsistencia de paquete confirmada: GioApiNotificationsConst vive en el paquete utils (sin prefijo com.hawkersco), tal como advierte el propio CLAUDE.md, que la documenta explícitamente como una inconsistencia conocida y aceptada, no un error a corregir.
  • Sin credenciales hardcodeadas: a diferencia de la mayoría de proyectos de este ecosistema, no existe un application.properties local con secretos reales — no se emite alerta de seguridad para este proyecto.