Compare commits

..

3 Commits

Author SHA1 Message Date
8a27d14d25 chore: remove temporary diagnostic workflow
All checks were successful
Deploy / deploy (push) Successful in 1m39s
2026-03-10 22:50:17 +01:00
4001f427ad temp: read impulsa production compose
All checks were successful
Deploy / deploy (push) Successful in 3m53s
2026-03-10 22:41:41 +01:00
1774518754 temp: find impulsa_landing image source and docker-compose
All checks were successful
Deploy / deploy (push) Successful in 4m9s
2026-03-10 22:36:43 +01:00
9 changed files with 200 additions and 179 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -1,64 +0,0 @@
name: Deploy API-Ubigeo
on:
push:
branches: [main]
jobs:
deploy:
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Backup current image
run: |
docker tag ubigeo-api:latest ubigeo-api:rollback 2>/dev/null || true
- name: Sync source
run: |
rsync -a --delete \
--exclude='.git' \
--exclude='node_modules' \
--exclude='dist' \
--exclude='.env*' \
./ /home/deployer/api-ubigeo/
- name: Build image
run: |
cd /home/deployer/api-ubigeo
docker build -t ubigeo-api:latest .
- name: Deploy containers
run: |
cd /home/deployer/api-ubigeo
docker compose -f docker-compose.production.yml down --remove-orphans
docker compose -f docker-compose.production.yml up -d
- name: Health check
id: health
continue-on-error: true
run: |
echo "Waiting for API-Ubigeo to start..."
for i in $(seq 1 15); do
sleep 3
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:3200/api/v1/health 2>/dev/null || echo "000")
if [ "$HTTP_CODE" != "000" ] && [ "$HTTP_CODE" != "502" ] && [ "$HTTP_CODE" != "503" ]; then
echo "✅ Health check passed after $((i*3))s (HTTP $HTTP_CODE)"
exit 0
fi
echo "Attempt $i/15 - not ready yet (HTTP $HTTP_CODE)..."
done
echo "❌ Health check failed after 45s"
docker logs ubigeo-api --tail 50
exit 1
- name: Rollback on failure
if: steps.health.outcome == 'failure'
run: |
echo "❌ Deploy failed, rolling back..."
docker tag ubigeo-api:rollback ubigeo-api:latest 2>/dev/null || true
cd /home/deployer/api-ubigeo
docker compose -f docker-compose.production.yml up -d --force-recreate
echo "🔄 Rollback complete"
exit 1

BIN
.github/.DS_Store vendored

Binary file not shown.

View File

@@ -6,15 +6,16 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Generate Prisma client
run: npx prisma generate
- name: Build
run: npm run build

66
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: self-hosted
steps:
- name: Checkout
run: |
if [ ! -d /workspace/darkcodex/api-ubigeo/.git ]; then
git clone https://948ee86882f8d9ca46eb94750addfccdace443a6@git.darkcodex.dev/darkcodex/api-ubigeo.git \
/workspace/darkcodex/api-ubigeo
fi
cd /workspace/darkcodex/api-ubigeo
git fetch origin main
git reset --hard origin/main
- name: Sync source
run: |
rsync -a --delete \
--exclude='.git' \
--exclude='node_modules' \
--exclude='dist' \
--exclude='.env' \
/workspace/darkcodex/api-ubigeo/ /home/deployer/api-ubigeo/
- name: Build image
run: |
cd /home/deployer/api-ubigeo
docker build -t ubigeo-api:latest .
- name: Deploy containers
run: |
cd /home/deployer/api-ubigeo
docker compose -f docker-compose.production.yml down --remove-orphans
docker compose -f docker-compose.production.yml up -d
- name: Register Traefik route
run: |
docker run --rm \
-v /etc/easypanel/traefik/config:/traefik-config \
alpine sh -c '
apk add jq -q --no-progress 2>/dev/null
FILE=/traefik-config/main.yaml
if jq -e ".http.routers[\"https-ubigeo-0\"]" "$FILE" > /dev/null 2>&1; then
echo "ubigeo route already exists in main.yaml"
exit 0
fi
jq ".http.routers[\"http-ubigeo-0\"] = {\"rule\": \"Host(\\\"api-ubigeo.darkcodex.dev\\\") && PathPrefix(\\\"/\\\")\", \"entryPoints\": [\"http\"], \"middlewares\": [\"redirect-to-https\", \"bad-gateway-error-page\"], \"service\": \"ubigeo_api-0\"} |
.http.routers[\"https-ubigeo-0\"] = {\"rule\": \"Host(\\\"api-ubigeo.darkcodex.dev\\\") && PathPrefix(\\\"/\\\")\", \"entryPoints\": [\"https\"], \"middlewares\": [\"bad-gateway-error-page\"], \"service\": \"ubigeo_api-0\", \"tls\": {\"certResolver\": \"letsencrypt\"}} |
.http.services[\"ubigeo_api-0\"] = {\"loadBalancer\": {\"passHostHeader\": true, \"servers\": [{\"url\": \"http://ubigeo-api:3200/\"}]}}" \
"$FILE" > /tmp/main-new.json && mv /tmp/main-new.json "$FILE"
echo "Traefik route injected into main.yaml"
'
- name: Health check
run: |
sleep 15
STATUS=$(wget -qO- http://ubigeo-api:3200/api/v1/health 2>/dev/null || echo "fail")
echo "Health: $STATUS"
echo "$STATUS" | grep -q "ok" && echo "Deploy OK" || echo "Warning: health check unreachable from runner"

View File

@@ -1,17 +0,0 @@
name: Sync to Gitea
on:
push:
branches: [main]
jobs:
sync:
name: Push to Gitea
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Push to Gitea
run: |
git push https://${{ secrets.GITEA_TOKEN }}@git.darkcodex.dev/darkcodex/api-ubigeo.git main --force

161
README.md
View File

@@ -1,121 +1,98 @@
# api-ubigeo
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
NestJS REST API for Peruvian geographic codes (UBIGEO) and world countries. Provides departments, provinces, and districts sourced from INEI 2025, with full-text search and Redis cache.
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
## Stack
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
- NestJS 11 + TypeScript 5.7
- Prisma 7 + PostgreSQL 16 (driver adapter pg)
- Redis (cache-manager + @keyv/redis)
- Swagger / OpenAPI
- Helmet + compression
- Docker + Docker Compose
## Description
## Features
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
- List and search all 25 Peruvian departments
- List and search provinces (filter by department)
- List and search districts (filter by province)
- Universal UBIGEO lookup by code (2, 4, or 6 digits)
- Full-text search across departments, provinces, and districts
- World countries endpoint with ISO codes, region, coordinates
- Filter countries by region
- HTTP cache headers (24h `Cache-Control` on all read endpoints)
- Swagger UI at `/api/docs`
- Health check endpoint
- Seeded database via `prisma/seed`
## Getting Started
### Prerequisites
- Node.js 22
- PostgreSQL 16
- Redis (optional, for distributed cache)
### Installation
## Project setup
```bash
npm install
$ npm install
```
### Database Setup
## Compile and run the project
```bash
# Run migrations
npm run prisma:migrate
# development
$ npm run start
# Generate Prisma client
npm run prisma:generate
# watch mode
$ npm run start:dev
# Seed the database
npm run seed
# production mode
$ npm run start:prod
```
### Running
## Run tests
```bash
# Development
npm run start:dev
# unit tests
$ npm run test
# Production
npm run start:prod
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
### Environment Variables
| Variable | Description | Required |
|----------|-------------|----------|
| `DATABASE_URL` | PostgreSQL connection string | Yes |
| `PORT` | Server port | No (default: `3200`) |
| `NODE_ENV` | Environment (`development` / `production`) | No |
## API Endpoints
All routes are prefixed with `/api/v1`.
### UBIGEO
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/v1/ubigeo/stats` | General stats (counts per level) |
| `GET` | `/api/v1/ubigeo/lookup/:codigo` | Universal lookup by code (2, 4, or 6 digits) |
| `GET` | `/api/v1/ubigeo/departamentos` | List all departments (supports `?q=` search) |
| `GET` | `/api/v1/ubigeo/departamentos/:codigo` | Get department with its provinces |
| `GET` | `/api/v1/ubigeo/provincias` | List provinces (supports `?q=` search, `?dep=` filter) |
| `GET` | `/api/v1/ubigeo/provincias/:codigo` | Get province with its districts |
| `GET` | `/api/v1/ubigeo/distritos` | List districts (supports `?q=` search, `?prov=` filter) |
| `GET` | `/api/v1/ubigeo/distritos/:codigo` | Get district by 6-digit code |
### Countries
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/v1/paises/stats` | Country statistics |
| `GET` | `/api/v1/paises/regiones` | List world regions |
| `GET` | `/api/v1/paises` | List countries (supports `?q=` search, `?region=` filter) |
| `GET` | `/api/v1/paises/:codigo` | Get country by ISO alpha-2 (PE) or alpha-3 (PER) |
### Health
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/v1/health` | Health check |
## Deployment
Docker Compose setup included for local development and production.
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
# Local development
docker-compose up -d
# Production
docker-compose -f docker-compose.production.yml up -d
$ npm install -g @nestjs/mau
$ mau deploy
```
Swagger documentation: `http://localhost:3200/api/docs`
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
## Resources
Check out a few resources that may come in handy when working with NestJS:
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Private
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

58
docs/active_context.md Normal file
View File

@@ -0,0 +1,58 @@
# Active Context - api-ubigeo
> Última actualización: 2026-03-10
## Estado Actual
**PRODUCCIÓN ACTIVA**`https://api-ubigeo.darkcodex.dev` responde correctamente desde internet.
- Health: `GET /api/v1/health``{"status":"ok"}`
- Distritos: `GET /api/v1/ubigeo/distritos?q=lima&limit=10` → HTTP 200
## Decisiones Recientes
### 2026-03-10
- **Traefik en Easypanel usa file provider con `main.yaml`** — NO es `directory:`, solo lee ese único archivo
- El archivo `api-ubigeo.yaml` separado que creamos antes era ignorado por Traefik
- **Solución**: inyectar los routers/services directamente en `main.yaml` usando `jq` vía `docker run -v`
- El runner de Gitea corre en un contenedor sin acceso directo al host filesystem, pero puede usar `docker run -v /host/path:/container/path` para acceder a rutas del host
- `main.yaml` está en JSON (no YAML real), Traefik lo acepta igual
- Routers agregados: `http-ubigeo-0` (redirect https) y `https-ubigeo-0` (TLS letsencrypt)
- Service: `ubigeo_api-0``http://ubigeo-api:3200/`
## Infraestructura
### Contenedor
- Imagen: `ubigeo-api:latest` (multi-stage build, node:20-alpine)
- Puerto interno: 3200
- Redes: `api-ubigeo_ubigeo_network` + `easypanel`
- Deploy path: `/home/deployer/api-ubigeo/`
### Traefik
- Config file: `/etc/easypanel/traefik/config/main.yaml` (JSON)
- El CI inyecta routers via `jq` con `docker run -v`
- Idempotente: verifica si `https-ubigeo-0` ya existe antes de agregar
### CI/CD
- Gitea runner: `contabo-runner` en `gitea_gitea_net`
- Runner corre en contenedor pero con acceso a Docker socket del host
- Workflow: `.github/workflows/deploy.yml`
- Run exitoso: #29
## Pendiente Próxima Sesión
- Nada crítico en api-ubigeo
- Pendiente en Impulsa Mobile: probar autocomplete de distrito con API live
## Contexto Técnico
### Workarounds
- `main.yaml` se sobreescribe en cada deploy de Easypanel → el CI debe reinjectar la ruta si eso ocurre
- El step `Register Traefik route` es idempotente (no duplica si ya existe)
### Archivos Clave
- `.github/workflows/deploy.yml` — CI/CD completo
- `docker-compose.production.yml` — producción con red easypanel
- `scripts/register-traefik.sh` — script legacy (ya no se usa, se puede eliminar)
- `src/modules/ubigeo/ubigeo.controller.ts` — endpoints
- `src/modules/ubigeo/dto/ubigeo.dto.ts` — DTOs

BIN
prisma/.DS_Store vendored

Binary file not shown.