docs: overhaul global documentation with diagrams, test scenarios, and agent directives
This commit is contained in:
parent
4a096ff903
commit
028d26d2e6
6 changed files with 404 additions and 2232 deletions
409
docs/AGENTS.md
409
docs/AGENTS.md
|
|
@ -1,382 +1,103 @@
|
||||||
# AGENTS.md - GoHorse Jobs
|
# AGENTS.md - GoHorse Jobs
|
||||||
|
|
||||||
> **Last Updated:** 2026-02-18
|
> **Purpose:** Context and Directives for AI coding assistants (Claude, Cursor, Aider, etc.)
|
||||||
> **Purpose:** Context for AI coding assistants (Claude, Cursor, etc.)
|
> **Branch Context:** `dev`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 SUPERIOR DIRECTIVES 🚨
|
||||||
|
|
||||||
|
1. **DO NOT TOUCH KUBERNETES/SECRETS**: You are strictly forbidden from modifying any files in `k8s/` or altering raw RSA/base64 authentication keys.
|
||||||
|
2. **ENV VARIABLES**: Never hardcode secrets in code. If you define a new environment variable, it must be documented. Next.js variables exposed to the client must use the `NEXT_PUBLIC_` prefix. NEVER expose secrets with `NEXT_PUBLIC_`.
|
||||||
|
3. **READ FIRST**: Before implementing new features, always read the relevant documentation in this `docs/` folder to understand existing patterns (e.g., Go Clean Architecture, React Server Components).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
GoHorse Jobs is a B2B SaaS recruitment platform connecting companies with candidates. It is structured as a monorepo with multiple services sharing a single PostgreSQL database.
|
GoHorse Jobs is a modern B2B SaaS recruitment platform connecting companies with candidates. It operates across multiple services sharing a single PostgreSQL database.
|
||||||
|
|
||||||
**Business model**: Freemium (Free / Pro R$199/month / Enterprise custom pricing).
|
**Business model**: Freemium (Free / Pro R$199/month / Enterprise custom pricing).
|
||||||
|
|
||||||
---
|
### Repository Structure
|
||||||
|
|
||||||
## Repository Structure
|
```text
|
||||||
|
|
||||||
```
|
|
||||||
gohorsejobs/
|
gohorsejobs/
|
||||||
├── backend/ # Go 1.24 REST API (Clean Architecture + DDD)
|
├── backend/ # Go REST API (Clean Architecture + DDD)
|
||||||
├── frontend/ # Next.js 15 web application (App Router)
|
├── frontend/ # Next.js web application (App Router)
|
||||||
├── backoffice/ # NestJS 11 admin/worker API (Fastify adapter)
|
├── backoffice/ # NestJS admin and worker API
|
||||||
├── seeder-api/ # Node.js Express database seeder
|
├── seeder-api/ # Node.js Express database seeder
|
||||||
├── job-scraper-multisite/# Job scraping service
|
├── job-scraper-multisite/# Job scraping service
|
||||||
├── k8s/ # Kubernetes manifests (dev, hml, prd)
|
├── k8s/ # Kubernetes manifests (FORBIDDEN DOMAIN)
|
||||||
├── docs/ # Central documentation
|
├── docs/ # Central documentation
|
||||||
├── .forgejo/ # Forgejo CI/CD workflows
|
├── .forgejo/ # Forgejo actions workflows
|
||||||
├── .drone.yml # Drone CI/CD pipelines
|
|
||||||
└── start.sh # Interactive dev startup script
|
└── start.sh # Interactive dev startup script
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
| Service | Language | Framework | Key Libraries |
|
|
||||||
|---------|----------|-----------|---------------|
|
|
||||||
| Backend | Go 1.24 | stdlib net/http | JWT v5, lib/pq, GORM, Stripe, AWS SDK v2, RabbitMQ, Firebase Admin, Swaggo |
|
|
||||||
| Frontend | TypeScript 5 | Next.js 15 (App Router) | React 19, Tailwind CSS 4, shadcn/ui, Zustand, React Hook Form + Zod, Framer Motion |
|
|
||||||
| Backoffice | TypeScript 5 | NestJS 11 (Fastify) | TypeORM, Passport + JWT, Stripe, AMQP, Nodemailer, Pino, Firebase Admin |
|
|
||||||
| Seeder | JavaScript (ESM) | Express 5 | pg, bcrypt |
|
|
||||||
|
|
||||||
**Database**: PostgreSQL 16+ with UUID v7 primary keys. 43+ SQL migration files in `backend/migrations/`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./start.sh # Interactive menu
|
|
||||||
```
|
|
||||||
|
|
||||||
| Option | Action |
|
|
||||||
|--------|--------|
|
|
||||||
| 1 | Start Frontend + Backend |
|
|
||||||
| 2 | Reset DB + Seed + Start |
|
|
||||||
| 3 | Start all services (Frontend + Backend + Backoffice) |
|
|
||||||
| 4 | Run migrations only |
|
|
||||||
| 5 | Seed database (append) |
|
|
||||||
| 6 | Full DB reset + migrate + seed |
|
|
||||||
| 7 | Run backend E2E tests |
|
|
||||||
| 8 | Seed reset LITE (skip 153k cities) |
|
|
||||||
| 9 | Run all tests (Backend + Frontend) |
|
|
||||||
|
|
||||||
### Service Ports
|
|
||||||
|
|
||||||
| Service | Port |
|
|
||||||
|---------|------|
|
|
||||||
| Backend | 8521 |
|
|
||||||
| Frontend | 8963 |
|
|
||||||
| Backoffice | 3001 |
|
|
||||||
| Swagger (Backend) | 8521/swagger/index.html |
|
|
||||||
| Swagger (Backoffice) | 3001/api/docs |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Build & Test Commands
|
|
||||||
|
|
||||||
### Backend (Go)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend && go run cmd/api/main.go # Run
|
|
||||||
go test -v ./... -count=1 # All unit tests
|
|
||||||
go test -tags=e2e -v ./tests/e2e/... # E2E tests
|
|
||||||
swag init -g cmd/api/main.go --parseDependency --parseInternal # Swagger
|
|
||||||
go mod tidy # Dependencies
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend (Next.js)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm install # Install deps
|
|
||||||
npm run dev # Dev server (use -p 8963 for project convention)
|
|
||||||
npm run build # Production build
|
|
||||||
npm run lint # ESLint
|
|
||||||
npm run test # Jest unit tests
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backoffice (NestJS)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backoffice
|
|
||||||
pnpm install # Uses pnpm
|
|
||||||
npm run start:dev # Dev server with watch
|
|
||||||
npm run build # Production build
|
|
||||||
npm run lint # ESLint with auto-fix
|
|
||||||
npm run test # Jest unit tests
|
|
||||||
npm run test:e2e # E2E tests
|
|
||||||
```
|
|
||||||
|
|
||||||
### Seeder
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd seeder-api
|
|
||||||
npm install
|
|
||||||
npm run migrate # Run migrations
|
|
||||||
npm run seed # Seed data
|
|
||||||
npm run seed:reset # Drop all tables
|
|
||||||
npm run seed:lite # Seed without 153k cities
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture & Conventions
|
## Architecture & Conventions
|
||||||
|
|
||||||
### Backend (Go) - Clean Architecture + DDD
|
### Backend (Go 1.24)
|
||||||
|
* **Architecture**: Clean Architecture + Domain Driven Design (DDD)
|
||||||
|
* **Flow**: Controller (`internal/handlers`) -> UseCase (`internal/core/usecases`) -> Interface Port (`internal/core/ports`) -> Repository (`internal/infrastructure/persistence`).
|
||||||
|
* **Key Libs**: `net/http` (stdlib routing), `lib/pq` + `gorm` (legacy/transitioning), `swaggo` (Docs).
|
||||||
|
* **Running**: `cd backend && go run cmd/api/main.go` (Port 8521)
|
||||||
|
|
||||||
```
|
### Frontend (Next.js 15)
|
||||||
backend/internal/
|
* **Architecture**: App Router (`src/app`), heavily favoring React Server Components (RSC). Use `'use client'` strictly at the leaf nodes of the component tree for interactivity.
|
||||||
├── api/ # (legacy) HTTP handlers
|
* **Styling**: Tailwind CSS, generic UI components in `src/components/ui` (shadcn).
|
||||||
├── handlers/ # HTTP request handlers (current)
|
* **State & Validation**: Zustand (global state), React Hook Form + Zod (forms).
|
||||||
├── middleware/ # Auth, CORS, rate limiting, security headers
|
* **i18n**: Multi-language support (PT, EN, ES, JA). Ensure all text is extracted to translation files.
|
||||||
├── core/
|
* **Running**: `cd frontend && npm run dev -p 8963` (Port 8963)
|
||||||
│ ├── domain/entity/ # Business entities (User, Company, Job, etc.)
|
|
||||||
│ ├── ports/ # Repository interfaces
|
|
||||||
│ └── usecases/ # Business logic (LoginUseCase, RegisterUseCase, etc.)
|
|
||||||
├── infrastructure/
|
|
||||||
│ ├── auth/ # JWT service
|
|
||||||
│ ├── persistence/ # PostgreSQL repository implementations
|
|
||||||
│ └── storage/ # S3/R2 storage adapter
|
|
||||||
├── services/ # Business services (Email, FCM, Storage, Admin)
|
|
||||||
├── dto/ # Data Transfer Objects
|
|
||||||
├── router/ # Route definitions
|
|
||||||
├── models/ # GORM models (legacy, being migrated)
|
|
||||||
├── database/ # DB connection & migration runner
|
|
||||||
└── utils/ # Utilities (JWT, sanitizer)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Patterns**:
|
### Backoffice (NestJS 11)
|
||||||
- Constructor injection: `func NewService(db *sql.DB) *Service`
|
* **Architecture**: Modular NestJS over Fastify.
|
||||||
- All DB operations accept `ctx context.Context`
|
* **Modules**: Admin, Auth, Email (Worker), Stripe, Tickets.
|
||||||
- Error handling: `(T, error)` return tuples
|
* **Running**: `cd backoffice && npm run start:dev` (Port 3001)
|
||||||
- Repository interfaces in `core/ports/`, implementations in `infrastructure/persistence/`
|
|
||||||
- Test files: `*_test.go`
|
|
||||||
|
|
||||||
### Frontend (Next.js) - App Router
|
### Seeder API (Express 5)
|
||||||
|
* **Purpose**: Manages database migrations and test data populations.
|
||||||
```
|
* **Running**: `cd seeder-api && npm run seed`
|
||||||
frontend/src/
|
|
||||||
├── app/ # File-based routing (20+ routes)
|
|
||||||
│ ├── dashboard/ # Protected routes (12+ sub-pages)
|
|
||||||
│ ├── jobs/ # Job listing & details
|
|
||||||
│ └── auth/ # Login, register flows
|
|
||||||
├── components/ # Reusable components (44+)
|
|
||||||
│ └── ui/ # shadcn/ui primitives (24+)
|
|
||||||
├── hooks/ # Custom React hooks
|
|
||||||
├── contexts/ # React contexts (auth, theme)
|
|
||||||
├── lib/ # Utilities (API calls, validation)
|
|
||||||
└── i18n/ # Internationalization (PT, EN, ES, JA)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Patterns**:
|
|
||||||
- Server Components by default; use `'use client'` for interactivity
|
|
||||||
- Tailwind CSS utility classes
|
|
||||||
- Path alias: `@/*` maps to `./src/*`
|
|
||||||
- Form validation: React Hook Form + Zod schemas
|
|
||||||
- Global state: Zustand stores
|
|
||||||
- Test files: `*.test.tsx`
|
|
||||||
|
|
||||||
### Backoffice (NestJS) - Modular
|
|
||||||
|
|
||||||
```
|
|
||||||
backoffice/src/
|
|
||||||
├── admin/ # Dashboard & statistics
|
|
||||||
├── auth/ # JWT authentication (Passport)
|
|
||||||
├── email/ # Email worker (AMQP/LavinMQ consumer)
|
|
||||||
├── external-services/ # Credential management
|
|
||||||
├── fcm-tokens/ # Firebase push tokens
|
|
||||||
├── plans/ # Subscription plans
|
|
||||||
├── stripe/ # Stripe payment integration
|
|
||||||
└── tickets/ # Support tickets
|
|
||||||
```
|
|
||||||
|
|
||||||
**Patterns**:
|
|
||||||
- NestJS module pattern: `*.module.ts`, `*.controller.ts`, `*.service.ts`
|
|
||||||
- Guards: `@UseGuards(JwtAuthGuard)`
|
|
||||||
- DTOs: `create-*.dto.ts`, `update-*.dto.ts` with class-validator
|
|
||||||
- Prettier: single quotes, trailing commas
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Authentication & Authorization
|
## ⚠️ Known Gotchas & Historical Errors
|
||||||
|
|
||||||
- **JWT**: HS256 with HttpOnly cookies (web) + Bearer tokens (API/mobile)
|
### 1. The `PASSWORD_PEPPER` Trap
|
||||||
- **4 roles**: `superadmin` > `admin` > `recruiter` > `candidate`
|
**Symptom**: "Invalid credentials" upon login right after seeding the database.
|
||||||
- **Middleware stack**: Auth (JWT+RBAC) -> CORS -> Rate Limiting (100 req/min) -> Security Headers -> XSS Sanitizer
|
**Cause**: The initial SQL migration creates the superadmin, but lacks the bcrypt pepper hashing. The Node.js `seeder-api` calculates the real hash.
|
||||||
- **JWT secret must match** between Backend and Backoffice
|
**Fix**: You must run `npm run migrate` **AND THEN** `npm run seed` in the `seeder-api` directory. The seeder will read `PASSWORD_PEPPER` (e.g., `gohorse-pepper`) from your `.env` and fix the hashes.
|
||||||
- **Test credentials**: See [TEST_USERS.md](TEST_USERS.md) for all test accounts
|
|
||||||
|
### 2. Login Payload
|
||||||
|
The frontend login form sends `{ "email": "admin", "password": "..." }`. The backend resolves this against the `email` or `identifier` columns. **Do not** change the payload payload to `{ "identifier": "admin" }` because the backend DTO explicitly expects the JSON key `"email"`.
|
||||||
|
|
||||||
|
### 3. Bcrypt in bash strings
|
||||||
|
**Never** run a raw SQL command via bash `docker exec` containing a bcrypt hash like `$2b$10$...`. The bash shell expands `$2b` as a variable, corrupting the hash. Always write the SQL to a file and execute the file.
|
||||||
|
|
||||||
|
### 4. Contact/Tickets
|
||||||
|
The contact form uses Next.js internationalization and routes directly to the backoffice `tickets` API. Emails go to `wtf@gohorsejobs.com`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Database
|
## Dev Environments & Test Users
|
||||||
|
|
||||||
- PostgreSQL 16+ with UUID v7 for primary keys (SERIAL for reference tables)
|
**Environments**:
|
||||||
- Migrations: `backend/migrations/` (43+ SQL files, numbered `000_` through `999_`)
|
* **dev**: Current working branch. Deployed to `dev.gohorsejobs.com` or `local.gohorsejobs.com`
|
||||||
- Core tables: `users`, `companies`, `user_companies`, `jobs`, `applications`, `favorite_jobs`, `notifications`, `tickets`, `activity_logs`, `job_payments`
|
* **main**: Production branch.
|
||||||
|
|
||||||
---
|
**Test Users** (Local/Dev):
|
||||||
|
* Superadmin: `lol` / `Admin@2025!`
|
||||||
|
* Superadmin: `superadmin` / `Admin@2025!`
|
||||||
|
|
||||||
## API Routes
|
See [docs/TEST_USERS.md](TEST_USERS.md) for Candidate and Company personas.
|
||||||
|
|
||||||
All backend routes under `/api/v1`:
|
|
||||||
- **Auth**: `/auth/login`, `/auth/register/candidate`, `/auth/register/company`, `/auth/forgot-password`, `/auth/reset-password`
|
|
||||||
- **Jobs**: CRUD at `/jobs`, moderation at `/jobs/moderation`
|
|
||||||
- **Companies**: CRUD at `/companies`, status management
|
|
||||||
- **Users**: `/users/me` (profile), admin CRUD at `/users`
|
|
||||||
- **Applications**: `/applications` with status updates
|
|
||||||
- **Storage**: `/storage/upload-url` (presigned S3/R2 URLs)
|
|
||||||
- **Admin**: `/admin/companies`, `/admin/email-templates`, `/admin/email-settings`
|
|
||||||
- **Notifications**: `/notifications`, `/tokens` (FCM)
|
|
||||||
- **Chat**: `/conversations`, `/conversations/{id}/messages`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## External Services
|
|
||||||
|
|
||||||
| Service | Purpose |
|
|
||||||
|---------|---------|
|
|
||||||
| Stripe | Payment processing & subscriptions |
|
|
||||||
| Firebase (FCM) | Push notifications |
|
|
||||||
| Appwrite | Real-time chat/messaging |
|
|
||||||
| LavinMQ (AMQP) | Message queue for background jobs |
|
|
||||||
| Cloudflare R2 / S3 | File/image storage |
|
|
||||||
| Resend | Transactional email |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
- **Environments**: `dev` (branch: dev), `hml` (branch: hml), `prd` (branch: main)
|
|
||||||
- **CI/CD**: Forgejo workflows (`.forgejo/workflows/`) + Drone (`.drone.yml`)
|
|
||||||
- **Container runtime**: Podman (Apolo), Coolify/Docker (Redbull), Kubernetes (production)
|
|
||||||
- **Registry**: Forgejo (`forgejo-gru.rede5.com.br/rede5/`)
|
|
||||||
- **Coolify UI**: https://redbull.rede5.com.br (Redbull VPS DEV environment)
|
|
||||||
|
|
||||||
### DEV Environments
|
|
||||||
|
|
||||||
| Server | Type | URLs |
|
|
||||||
|--------|------|------|
|
|
||||||
| **Redbull** (Coolify) | Auto-deploy via Git | `local.gohorsejobs.com`, `api-local.gohorsejobs.com` |
|
|
||||||
| **Apolo** (Podman/Quadlet) | Manual deploy | `dev.gohorsejobs.com`, `api-tmp.gohorsejobs.com` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Git Workflow
|
|
||||||
|
|
||||||
### Remotes
|
|
||||||
|
|
||||||
| Remote | URL | Function |
|
|
||||||
|--------|-----|----------|
|
|
||||||
| **origin** | git@github.com:rede5/gohorsejobs.git | GitHub - main development |
|
|
||||||
| **pipe** | https://pipe.gohorsejobs.com/bohessefm/gohorsejobs.git | Forgejo - mirror/CI |
|
|
||||||
|
|
||||||
### Branches
|
|
||||||
|
|
||||||
| Branch | Environment | Description |
|
|
||||||
|--------|-------------|-------------|
|
|
||||||
| **dev** | Development | Main working branch |
|
|
||||||
| **hml** | Staging | Pre-production testing |
|
|
||||||
| **main** | Production | Stable release |
|
|
||||||
|
|
||||||
### Sync Flow
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Pull from GitHub
|
|
||||||
git pull origin dev
|
|
||||||
|
|
||||||
# 2. Push to Forgejo (pipe)
|
|
||||||
git push pipe dev
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Key Things to Know
|
|
||||||
|
|
||||||
1. Backend uses Clean Architecture + DDD; respect layer boundaries (handlers -> usecases -> ports/repositories)
|
|
||||||
2. Frontend uses Next.js App Router; pages in `src/app/`, components in `src/components/`
|
|
||||||
3. Backoffice shares the same PostgreSQL database as the backend
|
|
||||||
4. Migrations are plain SQL files, not managed by an ORM
|
|
||||||
5. Project supports 4 languages (PT, EN, ES, JA) via i18n
|
|
||||||
6. Environment variables in `.env` files (gitignored)
|
|
||||||
7. Use `start.sh` for local development
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ Known Gotchas
|
|
||||||
|
|
||||||
### 1. PASSWORD_PEPPER e o hash do superadmin são gerados em runtime
|
|
||||||
|
|
||||||
A migration `010_seed_super_admin.sql` **não hardcoda hash**. Ela cria o superadmin com um
|
|
||||||
placeholder inválido e `force_change_password`. O **seeder-api** é quem gera o hash correto
|
|
||||||
em runtime lendo `process.env.PASSWORD_PEPPER`.
|
|
||||||
|
|
||||||
Fluxo correto após reset do banco:
|
|
||||||
```
|
|
||||||
npm run migrate → superadmin criado, hash = placeholder (não faz login)
|
|
||||||
npm run seed → hash recalculado com pepper do ambiente, status = active
|
|
||||||
```
|
|
||||||
|
|
||||||
O `start.sh` opções 2, 6 e 8 já fazem isso automaticamente (migrate → seed).
|
|
||||||
|
|
||||||
| Location | `PASSWORD_PEPPER` |
|
|
||||||
|----------|-------|
|
|
||||||
| `backend/.env` (local) | `gohorse-pepper` |
|
|
||||||
| `seeder-api/.env` | `gohorse-pepper` |
|
|
||||||
| Coolify DEV (`iw4sow8s0kkg4cccsk08gsoo`) | `gohorse-pepper` |
|
|
||||||
|
|
||||||
Se `PASSWORD_PEPPER` mudar, basta rodar `npm run seed` novamente — o hash é recalculado
|
|
||||||
automaticamente. Nenhuma migration precisa ser alterada.
|
|
||||||
|
|
||||||
If you suspect a mismatch, see the full fix procedure in [DEVOPS.md](DEVOPS.md#troubleshooting-login-retorna-invalid-credentials).
|
|
||||||
|
|
||||||
### 2. Login API field: `email`, not `identifier`
|
|
||||||
|
|
||||||
The frontend login form sends `{ "email": "<username_or_email>", "password": "..." }`.
|
|
||||||
The backend core DTO (`internal/core/dto/user_auth.go`) has field `Email`, **not** `Identifier`.
|
|
||||||
The repository resolves it via `WHERE email = $1 OR identifier = $1`, so both username and email work.
|
|
||||||
|
|
||||||
Do **not** send `{ "identifier": "..." }` directly to `/api/v1/auth/login` — the field will be ignored and login will fail silently.
|
|
||||||
|
|
||||||
### 3. Bcrypt hashes in SQL — always use a file, never `-c`
|
|
||||||
|
|
||||||
Shell interprets `$` in bcrypt hashes as variable expansions. Example of **broken** approach:
|
|
||||||
```bash
|
|
||||||
# ❌ WRONG — $2b gets expanded by shell, hash gets corrupted
|
|
||||||
docker exec postgres psql -U user -d db -c "UPDATE users SET password_hash='$2b$10$abc...'"
|
|
||||||
```
|
|
||||||
Correct approach — write to file first, then use `-f`:
|
|
||||||
```bash
|
|
||||||
cat > /tmp/fix.sql <<'EOF'
|
|
||||||
UPDATE users SET password_hash = '$2b$10$4759wJhnXnBpcwSnVZm9Eu.wTqGYVCHkxAU5a2NxhsFHU42nV3tzW' WHERE identifier = 'lol';
|
|
||||||
EOF
|
|
||||||
docker cp /tmp/fix.sql <postgres_container>:/tmp/fix.sql
|
|
||||||
docker exec <postgres_container> psql -U gohorsejobs -d gohorsejobs -f /tmp/fix.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Test credentials (DEV/local.gohorsejobs.com)
|
|
||||||
|
|
||||||
| Role | Identifier | Password | Email |
|
|
||||||
|------|-----------|----------|-------|
|
|
||||||
| superadmin | `lol` | `Admin@2025!` | lol@gohorsejobs.com |
|
|
||||||
| superadmin | `superadmin` | `Admin@2025!` | admin@gohorsejobs.com |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Documentation Index
|
## Documentation Index
|
||||||
|
|
||||||
| Document | Location | Description |
|
When asked to learn or modify parts of the system, consult these files first:
|
||||||
|----------|----------|-------------|
|
* [API.md](API.md) - Endpoint contracts
|
||||||
| API Reference | [API.md](API.md) | Endpoints, contracts, examples |
|
* [API_SECURITY.md](API_SECURITY.md) - Auth, RBAC
|
||||||
| API Security | [API_SECURITY.md](API_SECURITY.md) | Auth, RBAC, permissions |
|
* [APPSEC_STRATEGY.md](APPSEC_STRATEGY.md) - Application Security and Testing
|
||||||
| Database Schema | [DATABASE.md](DATABASE.md) | Tables, ERD, migrations |
|
* [DATABASE.md](DATABASE.md) - Schema and ERD
|
||||||
| DevOps | [DEVOPS.md](DEVOPS.md) | Infrastructure, deployment, diagrams |
|
* [DEVOPS.md](DEVOPS.md) - Cloudflare, Traefik, Containers
|
||||||
| Test Users | [TEST_USERS.md](TEST_USERS.md) | Credenciais de teste por role |
|
* [TASKS.md](TASKS.md) / [TEST_USERS.md](TEST_USERS.md)
|
||||||
| Roadmap | [ROADMAP.md](ROADMAP.md) | Product direction |
|
|
||||||
| Tasks | [TASKS.md](TASKS.md) | Task tracking |
|
|
||||||
| Workflows | [WORKFLOWS.md](WORKFLOWS.md) | Deployment workflows |
|
|
||||||
| Backend | [../backend/BACKEND.md](../backend/BACKEND.md) | Go API details |
|
|
||||||
| Frontend | [../frontend/FRONTEND.md](../frontend/FRONTEND.md) | Next.js details |
|
|
||||||
| Backoffice | [../backoffice/BACKOFFICE.md](../backoffice/BACKOFFICE.md) | NestJS details |
|
|
||||||
| Seeder | [../seeder-api/SEEDER-API.md](../seeder-api/SEEDER-API.md) | Database seeding & test data |
|
|
||||||
|
|
|
||||||
613
docs/API.md
613
docs/API.md
|
|
@ -1,574 +1,87 @@
|
||||||
# 📡 GoHorse Jobs - API Documentation
|
# 🔌 API Documentation & Routing - GoHorseJobs
|
||||||
|
|
||||||
Complete API reference with routes, permissions, and modules.
|
This document outlines the API landscape across the GoHorseJobs monorepo, detailing the main services and how they communicate.
|
||||||
|
|
||||||
> **Last Updated:** 2026-02-16
|
|
||||||
> **Base URL:** `https://api.gohorsejobs.com/api/v1`
|
|
||||||
> **Auth:** JWT Bearer Token or HttpOnly Cookie
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔐 Authentication
|
## 🏗️ API Architecture & Communication
|
||||||
|
|
||||||
### Methods
|
* **Backend API (Go):** The central source of truth for the platform. Handles all core business logic (Users, Companies, Jobs, Applications).
|
||||||
1. **Authorization Header:** `Authorization: Bearer <token>`
|
* **Backoffice API (NestJS):** Dedicated to administrative tasks, workers (email queues), and specialized integrations (Stripe, Firebase Cloud Messaging). Shares the **same PostgreSQL database** as the Backend API.
|
||||||
2. **Cookie:** `jwt=<token>` (HttpOnly, Secure)
|
* **Seeder API (Node.js):** Runs strictly on demand to populate the database with dummy data or run migrations.
|
||||||
|
|
||||||
### Roles
|
### Communication Flow (Frontend)
|
||||||
| Role | Code | Level | Description |
|
The Next.js Frontend primarily communicates with the **Backend API**.
|
||||||
|------|------|-------|-------------|
|
* **Paradigm:** The frontend uses React Server Components (RSC) to fetch data natively from the Go API.
|
||||||
| **SuperAdmin** | `superadmin` | 0 | Platform administrator |
|
* **Client Actions:** For forms and interactivity (e.g., login, apply), client components dispatch requests using the wrapped fetch utility located at `src/lib/api.ts`.
|
||||||
| **Admin** | `admin` | 1 | Company administrator |
|
* **Auth State:** JWT tokens are stored locally (Cookies/Storage) and appended to the `Authorization: Bearer <token>` header manually via interceptors or utility functions.
|
||||||
| **Recruiter** | `recruiter` | 2 | Job poster |
|
|
||||||
| **Candidate** | `candidate` | 3 | Candidate |
|
|
||||||
| **Guest** | - | - | No authentication |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 Module: Authentication
|
## 📡 Backend API Reference (`/api/v1`)
|
||||||
|
Hosted at `api-local.gohorsejobs.com` (Dev) or `api.gohorsejobs.com` (Prd).
|
||||||
|
View detailed interactive Swagger Docs by visiting `/swagger/index.html`.
|
||||||
|
|
||||||
### Login
|
### Authentication
|
||||||
```http
|
* `POST /auth/login`: Accepts `{ "email": "...", "password": "..."}`. Returns JWT token and User DTO.
|
||||||
POST /api/v1/auth/login
|
* `POST /auth/register/candidate`: Creates a standard user.
|
||||||
```
|
* `POST /auth/register/company`: Creates a user and a linked `Company` profile awaiting approval.
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | Guest | Authenticate user |
|
|
||||||
|
|
||||||
**Request:**
|
### Users & Profiles
|
||||||
```json
|
* `GET /users/me`: Gets the full profile of the currently authenticated user (derived from JWT context).
|
||||||
{
|
* `PUT /users/me`: Updates profile details.
|
||||||
"identifier": "lol",
|
* `GET /admin/users`: (Admin only) Lists all users.
|
||||||
"password": "Admin@2025!"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response (200):**
|
### Companies
|
||||||
```json
|
* `GET /companies`: Lists active companies with pagination and filters.
|
||||||
{
|
* `GET /companies/{id}`: Detailed company view.
|
||||||
"token": "eyJhbGciOiJI...",
|
* `PUT /companies/{id}`: Edit company profile (Requires owner/admin permissions via `user_companies` relation).
|
||||||
"user": { "id": 1, "role": "superadmin", "name": "Dr. Horse Expert" }
|
* `POST /admin/companies/{id}/status`: (Admin only) Approve/Reject pending companies.
|
||||||
}
|
|
||||||
```
|
### Jobs & Applications
|
||||||
|
* `GET /jobs`: Lists published jobs. (Supports full-text search and filters).
|
||||||
|
* `POST /jobs`: Creates a draft job (Requires Recruiter).
|
||||||
|
* `PUT /jobs/{id}`: Updates job details.
|
||||||
|
* `POST /jobs/{id}/apply`: Submits an application for the job.
|
||||||
|
* `GET /applications`: Lists received applications (for recruiters) or submitted applications (for candidates).
|
||||||
|
* `POST /applications/{id}/status`: Updates application status (`reviewing`, `accepted`, `rejected`).
|
||||||
|
|
||||||
|
### Miscellaneous
|
||||||
|
* `GET /health`: Basic ping endpoint.
|
||||||
|
* `POST /storage/upload-url`: Requests a presigned URL (S3/Cloudflare R2) to upload logos or resumes directly from the browser.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Register Candidate
|
## 🛠️ Backoffice API Reference (`/api`)
|
||||||
```http
|
Hosted at `b-local.gohorsejobs.com` (Dev) or `b.gohorsejobs.com` (Prd).
|
||||||
POST /api/v1/auth/register
|
View detailed interactive Swagger Docs by visiting `/api/docs`.
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | Guest | Register new job seeker |
|
|
||||||
|
|
||||||
**Request:**
|
### Tickets & Support
|
||||||
```json
|
* `POST /tickets`: Public endpoint (no auth required) to submit a contact/support ticket. Used directly by the frontend `/contact` page.
|
||||||
{
|
* `GET /tickets`: (Admin only) Lists all open tickets.
|
||||||
"name": "John Doe",
|
* `POST /tickets/{id}/reply`: (Admin only) Adds a message to a ticket and triggers an email notification to the user.
|
||||||
"email": "john@example.com",
|
|
||||||
"password": "SecurePass123!"
|
### Emails & Workers
|
||||||
}
|
The Backoffice listens to a LavinMQ (AMQP) message queue to dispatch transactional emails (welcome, password reset, application updates) securely without blocking the main Go runtime.
|
||||||
```
|
|
||||||
|
### Stripe Billing
|
||||||
|
* `POST /stripe/webhook`: Consumes Stripe events to upgrade company plans from Free to Pro/Enterprise.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🏢 Module: Companies
|
## 📚 Seeder API Commands
|
||||||
|
|
||||||
### Create Company
|
If you need to instantly provision the database for these routes to work:
|
||||||
```http
|
|
||||||
POST /api/v1/companies
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | Guest | Register new company |
|
|
||||||
|
|
||||||
### List Companies
|
```bash
|
||||||
```http
|
cd seeder-api
|
||||||
GET /api/v1/companies
|
npm install
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Smart | ⚪ Optional | Guest/Admin | Public list or admin full list |
|
|
||||||
|
|
||||||
### Update Company Status
|
# Drops all tables, runs migrations, inserts core test users (100 jobs, 50 companies)
|
||||||
```http
|
npm run seed:lite
|
||||||
PATCH /api/v1/companies/{id}/status
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | Activate/deactivate company |
|
|
||||||
|
|
||||||
---
|
# Runs migrations ONLY
|
||||||
|
npm run migrate
|
||||||
|
```
|
||||||
|
|
||||||
## 👥 Module: Users
|
The seeder automatically inserts the `Admin@2025!` superadmin hash required for local testing.
|
||||||
|
|
||||||
### Get Current User
|
|
||||||
```http
|
|
||||||
GET /api/v1/users/me
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | Any authenticated | Get current user profile |
|
|
||||||
|
|
||||||
### List Users
|
|
||||||
```http
|
|
||||||
GET /api/v1/users
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | List all users |
|
|
||||||
|
|
||||||
### Create User
|
|
||||||
```http
|
|
||||||
POST /api/v1/users
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | Create new user |
|
|
||||||
|
|
||||||
### Update User
|
|
||||||
```http
|
|
||||||
PATCH /api/v1/users/{id}
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | Any authenticated | Update user profile |
|
|
||||||
|
|
||||||
### Delete User
|
|
||||||
```http
|
|
||||||
DELETE /api/v1/users/{id}
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin | Delete user |
|
|
||||||
|
|
||||||
### List User Roles
|
|
||||||
```http
|
|
||||||
GET /api/v1/users/roles
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | List available roles |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💼 Module: Jobs
|
|
||||||
|
|
||||||
### List Jobs (Public)
|
|
||||||
```http
|
|
||||||
GET /api/v1/jobs
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | Guest | Search and filter jobs |
|
|
||||||
|
|
||||||
**Query Parameters:**
|
|
||||||
| Param | Type | Description |
|
|
||||||
|-------|------|-------------|
|
|
||||||
| `q` | string | Search query (title, description) |
|
|
||||||
| `location` | string | Filter by location |
|
|
||||||
| `type` | string | Employment type filter |
|
|
||||||
| `workMode` | string | `onsite`, `hybrid`, `remote` |
|
|
||||||
| `page` | int | Page number (default: 1) |
|
|
||||||
| `limit` | int | Items per page (default: 20) |
|
|
||||||
|
|
||||||
### Get Job by ID
|
|
||||||
```http
|
|
||||||
GET /api/v1/jobs/{id}
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | Guest | Get job details |
|
|
||||||
|
|
||||||
### Create Job
|
|
||||||
```http
|
|
||||||
POST /api/v1/jobs
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public* | ❌ | admin, recruiter | Create new job posting |
|
|
||||||
|
|
||||||
### Update Job
|
|
||||||
```http
|
|
||||||
PUT /api/v1/jobs/{id}
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public* | ❌ | admin, recruiter | Update job posting |
|
|
||||||
|
|
||||||
### Delete Job
|
|
||||||
```http
|
|
||||||
DELETE /api/v1/jobs/{id}
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public* | ❌ | admin, recruiter | Delete job posting |
|
|
||||||
|
|
||||||
### List Jobs for Moderation
|
|
||||||
```http
|
|
||||||
GET /api/v1/jobs/moderation
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | List all jobs for moderation |
|
|
||||||
|
|
||||||
### Update Job Status
|
|
||||||
```http
|
|
||||||
PATCH /api/v1/jobs/{id}/status
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | Approve/reject/close job |
|
|
||||||
|
|
||||||
### Duplicate Job
|
|
||||||
```http
|
|
||||||
POST /api/v1/jobs/{id}/duplicate
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | Duplicate job posting |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Module: Applications
|
|
||||||
|
|
||||||
### Create Application
|
|
||||||
```http
|
|
||||||
POST /api/v1/applications
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | Guest/JobSeeker | Apply to a job |
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"job_id": 123,
|
|
||||||
"name": "John Doe",
|
|
||||||
"email": "john@example.com",
|
|
||||||
"phone": "+55 11 99999-9999",
|
|
||||||
"message": "I am interested in this position...",
|
|
||||||
"resume_url": "https://..."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### List Applications
|
|
||||||
```http
|
|
||||||
GET /api/v1/applications
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | admin, recruiter | List applications |
|
|
||||||
|
|
||||||
### Get Application by ID
|
|
||||||
```http
|
|
||||||
GET /api/v1/applications/{id}
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | admin, recruiter | Get application details |
|
|
||||||
|
|
||||||
### Update Application Status
|
|
||||||
```http
|
|
||||||
PUT /api/v1/applications/{id}/status
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | admin, recruiter | Update application status |
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "hired",
|
|
||||||
"notes": "Approved after final interview"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Status Values:**
|
|
||||||
- `pending` - Awaiting review
|
|
||||||
- `reviewed` - Seen by recruiter
|
|
||||||
- `shortlisted` - Selected for interview
|
|
||||||
- `rejected` - Not selected
|
|
||||||
- `hired` - Offer accepted
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔔 Module: Notifications
|
|
||||||
|
|
||||||
### List Notifications
|
|
||||||
```http
|
|
||||||
GET /api/v1/notifications
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | Any authenticated | Get user notifications |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏷️ Module: Tags
|
|
||||||
|
|
||||||
### List Tags
|
|
||||||
```http
|
|
||||||
GET /api/v1/tags
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | List all tags |
|
|
||||||
|
|
||||||
### Create Tag
|
|
||||||
```http
|
|
||||||
POST /api/v1/tags
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | Create new tag |
|
|
||||||
|
|
||||||
### Update Tag
|
|
||||||
```http
|
|
||||||
PATCH /api/v1/tags/{id}
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | Update tag |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 👤 Module: Candidates
|
|
||||||
|
|
||||||
### List Candidates
|
|
||||||
```http
|
|
||||||
GET /api/v1/candidates
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | List all candidates |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Module: Audit
|
|
||||||
|
|
||||||
### List Login Audits
|
|
||||||
```http
|
|
||||||
GET /api/v1/audit/logins
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | Login audit trail |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Module: Storage
|
|
||||||
|
|
||||||
### Generate Upload URL
|
|
||||||
```http
|
|
||||||
POST /api/v1/storage/upload-url
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | Any authenticated | Get S3 presigned upload URL |
|
|
||||||
|
|
||||||
### Generate Download URL
|
|
||||||
```http
|
|
||||||
POST /api/v1/storage/download-url
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | Any authenticated | Get S3 presigned download URL |
|
|
||||||
|
|
||||||
### Delete File
|
|
||||||
```http
|
|
||||||
DELETE /api/v1/storage/files
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | Any authenticated | Delete file from S3 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Module: Documentation
|
|
||||||
|
|
||||||
### Swagger UI
|
|
||||||
```http
|
|
||||||
GET /docs/
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Public | ❌ | Guest | Interactive API documentation |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔑 Module: System Credentials
|
|
||||||
|
|
||||||
Manage encrypted credentials for external services (Stripe, SMTP, Storage, etc.).
|
|
||||||
|
|
||||||
### List Configured Services
|
|
||||||
```http
|
|
||||||
GET /api/v1/system/credentials
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | List all configured services (metadata only, no secrets) |
|
|
||||||
|
|
||||||
**Response (200):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"services": [
|
|
||||||
{
|
|
||||||
"service_name": "stripe",
|
|
||||||
"updated_at": "2026-02-23T10:30:00Z",
|
|
||||||
"updated_by": "admin-uuid",
|
|
||||||
"is_configured": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"service_name": "smtp",
|
|
||||||
"updated_at": "2026-02-22T14:00:00Z",
|
|
||||||
"updated_by": "superadmin-uuid",
|
|
||||||
"is_configured": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Save Credentials
|
|
||||||
```http
|
|
||||||
POST /api/v1/system/credentials
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | Create or update service credentials |
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"serviceName": "stripe",
|
|
||||||
"payload": {
|
|
||||||
"secretKey": "sk_live_xxx",
|
|
||||||
"webhookSecret": "whsec_xxx",
|
|
||||||
"publishableKey": "pk_live_xxx"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response (200):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Credentials saved successfully"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Supported Services:**
|
|
||||||
| Service Name | Fields |
|
|
||||||
|--------------|--------|
|
|
||||||
| `stripe` | `secretKey`, `webhookSecret`, `publishableKey` |
|
|
||||||
| `smtp` | `host`, `port`, `username`, `password`, `from_email`, `from_name`, `secure` |
|
|
||||||
| `storage` | `endpoint`, `region`, `bucket`, `accessKey`, `secretKey` |
|
|
||||||
| `cloudflare_config` | `apiToken`, `zoneId` |
|
|
||||||
| `firebase` | `serviceAccountJson` (JSON string) |
|
|
||||||
| `appwrite` | `endpoint`, `projectId`, `apiKey` |
|
|
||||||
| `lavinmq` | `amqpUrl` |
|
|
||||||
| `cpanel` | `host`, `username`, `apiToken` |
|
|
||||||
|
|
||||||
### Delete Credentials
|
|
||||||
```http
|
|
||||||
DELETE /api/v1/system/credentials/{serviceName}
|
|
||||||
```
|
|
||||||
| Field | Auth | Roles | Description |
|
|
||||||
|-------|------|-------|-------------|
|
|
||||||
| Protected | ✅ | superadmin, admin | Delete service credentials |
|
|
||||||
|
|
||||||
**Response (200):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Security Notes:**
|
|
||||||
- Credentials are encrypted with RSA-OAEP-SHA256 before storage
|
|
||||||
- Only metadata (service name, timestamps) is returned on list operations
|
|
||||||
- Actual secret values cannot be retrieved after saving
|
|
||||||
- Updating credentials overwrites the existing configuration
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔑 Permission Matrix
|
|
||||||
|
|
||||||
| Route | Guest | JobSeeker | Recruiter | CompanyAdmin | Admin | SuperAdmin |
|
|
||||||
|-------|-------|-----------|-----------|--------------|-------|------------|
|
|
||||||
| `POST /auth/login` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
||||||
| `POST /auth/register` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
||||||
| `GET /jobs` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
||||||
| `GET /jobs/{id}` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
||||||
| `POST /jobs` | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
|
|
||||||
| `POST /applications` | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
||||||
| `GET /users/me` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
||||||
| `GET /users` | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
|
|
||||||
| `DELETE /users/{id}` | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
||||||
| `GET /notifications` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
||||||
| `GET /audit/logins` | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
|
|
||||||
| `GET /jobs/moderation` | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🆔 ID Formats
|
|
||||||
|
|
||||||
| Entity | ID Type | Example |
|
|
||||||
|--------|---------|---------|
|
|
||||||
| Users | INT (SERIAL) | `1`, `42`, `1337` |
|
|
||||||
| Companies | INT (SERIAL) | `1`, `15`, `100` |
|
|
||||||
| Jobs | INT (SERIAL) | `1`, `500`, `2500` |
|
|
||||||
| Notifications | UUID v7 | `019438a1-2b3c-7abc-8123-4567890abcdef` |
|
|
||||||
| Tickets | UUID v7 | `019438a2-3c4d-7xyz-9abc-def0123456789` |
|
|
||||||
| Payments | UUID v7 | `019438a3-4e5f-7mno-pqrs-tuvwxyz012345` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Error Responses
|
|
||||||
|
|
||||||
### 400 Bad Request
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Invalid request body",
|
|
||||||
"details": "Field 'email' is required"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 401 Unauthorized
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Unauthorized",
|
|
||||||
"message": "Invalid or expired token"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 403 Forbidden
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Forbidden",
|
|
||||||
"message": "Insufficient permissions"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 404 Not Found
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Not Found",
|
|
||||||
"message": "Resource not found"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 500 Internal Server Error
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Internal Server Error",
|
|
||||||
"message": "An unexpected error occurred"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Related Documentation
|
|
||||||
|
|
||||||
- [Database Schema](DATABASE.md)
|
|
||||||
- [Roadmap](ROADMAP.md)
|
|
||||||
- [Tasks](TASKS.md)
|
|
||||||
|
|
|
||||||
798
docs/DATABASE.md
798
docs/DATABASE.md
|
|
@ -1,751 +1,131 @@
|
||||||
# 🗄️ Database Schema Documentation
|
# 🗄️ Database Architecture - GoHorseJobs
|
||||||
|
|
||||||
Complete database documentation for the GoHorseJobs platform.
|
GoHorseJobs uses a single **PostgreSQL 16+** database shared across all services (Backend API, NestJS Backoffice, Node.js Seeder).
|
||||||
|
|
||||||
> **Last Updated:** 2026-02-16
|
|
||||||
> **Database:** PostgreSQL 16+ (Local `postgres-main` container)
|
|
||||||
> **Connection:** Internal `gohorsejobs_dev` database via `web_proxy` network
|
|
||||||
> **ID Strategy:** UUID v7 for core tables, SERIAL for reference tables
|
|
||||||
> **Migrations:** 30 SQL files in `backend/migrations/`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## <EFBFBD>️ Development Environment Structure
|
## 🏗️ Core Entity-Relationship Diagram (ERD)
|
||||||
|
|
||||||
The development environment (`apolo` server) uses a **Local Containerized Strategy** to ensure isolation and speed.
|
The core data model centers around `users` acting in different capacities (`candidate`, `recruiter`, `admin`) interacting with `companies` and `jobs`.
|
||||||
|
|
||||||
### 🏗️ Topology
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
subgraph VPS ["Apolo Server (VPS)"]
|
|
||||||
subgraph Net ["Docker Network: web_proxy"]
|
|
||||||
PG[("postgres-main")]
|
|
||||||
BE["Backend API"]
|
|
||||||
BO["Backoffice"]
|
|
||||||
SE["Seeder API"]
|
|
||||||
end
|
|
||||||
Traefik["Traefik Proxy"]
|
|
||||||
end
|
|
||||||
|
|
||||||
Traefik --> BE
|
|
||||||
Traefik --> BO
|
|
||||||
Traefik --> SE
|
|
||||||
|
|
||||||
BE -- "internal:5432" --> PG
|
|
||||||
BO -- "internal:5432" --> PG
|
|
||||||
SE -- "internal:5432" --> PG
|
|
||||||
|
|
||||||
style PG fill:#336791,stroke:#fff,stroke-width:2px,color:#fff
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔌 Connection Details
|
|
||||||
|
|
||||||
All services connect to the database via the internal Docker network.
|
|
||||||
|
|
||||||
| Parameter | Value | Notes |
|
|
||||||
|-----------|-------|-------|
|
|
||||||
| **Host** | `postgres-main` | Internal Container Hostname |
|
|
||||||
| **Port** | `5432` | Internal Port |
|
|
||||||
| **Database** | `gohorsejobs_dev` | Dedicated Dev DB (Isolated from `main_db`) |
|
|
||||||
| **User** | `yuki` | Owner of public schema |
|
|
||||||
| **Network** | `web_proxy` | Shared Bridge Network |
|
|
||||||
| **SSL Mode** | `disable` | Internal traffic is unencrypted |
|
|
||||||
|
|
||||||
### 🚀 Access & Management
|
|
||||||
|
|
||||||
Since the database runs inside a container and is not exposed to the public internet, use the following methods for access:
|
|
||||||
|
|
||||||
**1. CLI Access (via SSH)**
|
|
||||||
```bash
|
|
||||||
# Connect to PostgreSQL shell
|
|
||||||
ssh root@apolo 'podman exec -it postgres-main psql -U yuki -d gohorsejobs_dev'
|
|
||||||
```
|
|
||||||
|
|
||||||
**2. Run Migrations**
|
|
||||||
Migrations are applied using the Backend service or manually piped:
|
|
||||||
```bash
|
|
||||||
# Manual Pipe (from local machine)
|
|
||||||
cat backend/migrations/*.sql | ssh root@apolo 'podman exec -i postgres-main psql -U yuki -d gohorsejobs_dev'
|
|
||||||
```
|
|
||||||
|
|
||||||
**3. Seeding Data**
|
|
||||||
Trigger the Seeder API (running locally) to populate data:
|
|
||||||
```bash
|
|
||||||
curl -X POST https://seeder.gohorsejobs.com/seed
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## <20>📊 Entity Relationship Diagram
|
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
erDiagram
|
erDiagram
|
||||||
%% Core Entities
|
USERS ||--o{ USER_COMPANIES : "belongs to"
|
||||||
users ||--o{ user_companies : "belongs to"
|
COMPANIES ||--o{ USER_COMPANIES : "contains"
|
||||||
users ||--o{ user_roles : "has roles"
|
|
||||||
users ||--o{ applications : "submits"
|
|
||||||
users ||--o{ favorite_jobs : "saves"
|
|
||||||
users ||--o{ notifications : "receives"
|
|
||||||
users ||--o{ tickets : "opens"
|
|
||||||
users ||--o{ ticket_messages : "sends"
|
|
||||||
users ||--o{ login_audits : "generates"
|
|
||||||
users ||--o{ activity_logs : "generates"
|
|
||||||
|
|
||||||
companies ||--o{ user_companies : "has members"
|
COMPANIES ||--o{ JOBS : "posts"
|
||||||
companies ||--o{ jobs : "posts"
|
USERS ||--o{ JOBS : "creates (author)"
|
||||||
|
|
||||||
jobs ||--o{ applications : "receives"
|
USERS ||--o{ APPLICATIONS : "submits"
|
||||||
jobs ||--o{ favorite_jobs : "saved by"
|
JOBS ||--o{ APPLICATIONS : "receives"
|
||||||
jobs ||--o{ job_payments : "has payments"
|
|
||||||
|
|
||||||
regions ||--o{ cities : "contains"
|
USERS ||--o{ FAVORITE_JOBS : "saves"
|
||||||
regions ||--o{ companies : "located in"
|
JOBS ||--o{ FAVORITE_JOBS : "favorited"
|
||||||
regions ||--o{ jobs : "located in"
|
|
||||||
|
|
||||||
tickets ||--o{ ticket_messages : "contains"
|
USERS ||--o{ TICKETS : "opens"
|
||||||
|
USERS ||--o{ TICKET_MESSAGES : "sends"
|
||||||
|
TICKETS ||--o{ TICKET_MESSAGES : "has"
|
||||||
|
|
||||||
%% Entities
|
USERS {
|
||||||
users {
|
uuid id PK
|
||||||
int id PK "SERIAL"
|
string role "superadmin, admin, recruiter, candidate"
|
||||||
varchar identifier UK "login"
|
string identifier "username or email"
|
||||||
varchar password_hash
|
string email UK
|
||||||
varchar role "enum"
|
string password_hash
|
||||||
varchar full_name
|
boolean is_active
|
||||||
varchar email
|
timestamp created_at
|
||||||
varchar name
|
|
||||||
int tenant_id FK "nullable"
|
|
||||||
varchar status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user_roles {
|
COMPANIES {
|
||||||
int user_id PK,FK
|
uuid id PK
|
||||||
varchar role PK "composite key"
|
string name
|
||||||
|
string document "CNPJ"
|
||||||
|
string domain UK
|
||||||
|
string location
|
||||||
|
string industry
|
||||||
|
string logo_url
|
||||||
|
string status "pending, active, rejected"
|
||||||
}
|
}
|
||||||
|
|
||||||
companies {
|
USER_COMPANIES {
|
||||||
int id PK "SERIAL"
|
uuid id PK
|
||||||
varchar name
|
uuid user_id FK
|
||||||
varchar slug UK
|
uuid company_id FK
|
||||||
varchar type
|
string role "owner, admin, member"
|
||||||
varchar document
|
|
||||||
text address
|
|
||||||
int region_id FK
|
|
||||||
varchar email
|
|
||||||
varchar website
|
|
||||||
boolean verified
|
|
||||||
boolean active
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jobs {
|
JOBS {
|
||||||
int id PK "SERIAL"
|
uuid id PK
|
||||||
int company_id FK
|
uuid company_id FK
|
||||||
int created_by FK
|
uuid author_id FK
|
||||||
varchar title
|
string title
|
||||||
text description
|
text description
|
||||||
decimal salary_min
|
string type "full-time, remote, etc"
|
||||||
decimal salary_max
|
decimal max_salary
|
||||||
varchar salary_type
|
decimal min_salary
|
||||||
varchar currency
|
string status "draft, published, closed"
|
||||||
varchar employment_type
|
|
||||||
varchar work_mode
|
|
||||||
varchar status
|
|
||||||
boolean is_featured
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applications {
|
APPLICATIONS {
|
||||||
int id PK "SERIAL"
|
|
||||||
int job_id FK
|
|
||||||
int user_id FK "nullable"
|
|
||||||
varchar status
|
|
||||||
text message
|
|
||||||
varchar resume_url
|
|
||||||
}
|
|
||||||
|
|
||||||
regions {
|
|
||||||
int id PK "SERIAL"
|
|
||||||
varchar name
|
|
||||||
varchar country_code
|
|
||||||
varchar code
|
|
||||||
}
|
|
||||||
|
|
||||||
cities {
|
|
||||||
int id PK "SERIAL"
|
|
||||||
int region_id FK
|
|
||||||
varchar name
|
|
||||||
}
|
|
||||||
|
|
||||||
notifications {
|
|
||||||
uuid id PK
|
uuid id PK
|
||||||
int user_id FK
|
uuid job_id FK
|
||||||
varchar type
|
uuid candidate_id FK
|
||||||
varchar title
|
string status "pending, reviewing, interviewed, rejected, accepted"
|
||||||
text message
|
text cover_letter
|
||||||
boolean read
|
|
||||||
}
|
}
|
||||||
|
|
||||||
job_payments {
|
FAVORITE_JOBS {
|
||||||
uuid id PK
|
uuid id PK
|
||||||
int job_id FK
|
uuid user_id FK
|
||||||
int user_id FK
|
uuid job_id FK
|
||||||
decimal amount
|
timestamp created_at
|
||||||
varchar status
|
|
||||||
varchar stripe_session_id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tickets {
|
TICKETS {
|
||||||
uuid id PK
|
uuid id PK
|
||||||
int user_id FK
|
uuid user_id FK "nullable for guests"
|
||||||
varchar subject
|
string subject
|
||||||
varchar status
|
string category "Support, Bug, Billing..."
|
||||||
varchar priority
|
string status "open, in_progress, resolved, closed"
|
||||||
}
|
string email "fallback for guests"
|
||||||
|
string name "fallback for guests"
|
||||||
ticket_messages {
|
|
||||||
uuid id PK
|
|
||||||
uuid ticket_id FK
|
|
||||||
int user_id FK
|
|
||||||
text message
|
|
||||||
boolean is_staff
|
|
||||||
}
|
|
||||||
|
|
||||||
job_posting_prices {
|
|
||||||
int id PK
|
|
||||||
varchar name
|
|
||||||
decimal price
|
|
||||||
int duration_days
|
|
||||||
}
|
|
||||||
|
|
||||||
login_audits {
|
|
||||||
int id PK
|
|
||||||
int user_id FK
|
|
||||||
varchar identifier
|
|
||||||
boolean success
|
|
||||||
varchar ip_address
|
|
||||||
}
|
|
||||||
|
|
||||||
activity_logs {
|
|
||||||
int id PK
|
|
||||||
int user_id FK
|
|
||||||
varchar entity_type
|
|
||||||
varchar action
|
|
||||||
jsonb details
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🏗️ Table Overview
|
## 🔑 Key Strategies
|
||||||
|
|
||||||
| Table | ID Type | Description | Migration |
|
### 1. Primary Keys (UUID v7)
|
||||||
|-------|---------|-------------|-----------|
|
We aggressively use **UUID v7** for all major business entities (`users`, `companies`, `jobs`).
|
||||||
| `users` | UUID v7 | System users (candidates, recruiters, admins) | 001/021 |
|
* **Why?** UUID v7 contains a timestamp component, making it naturally sortable by insertion time (like SERIAL/auto-increment) while avoiding the predictability and distributed generation bottlenecks of standard integers.
|
||||||
| `user_roles` | Composite | Additional roles per user | 020 |
|
|
||||||
| `companies` | UUID v7 | Employer organizations | 002/021 |
|
|
||||||
| `user_companies` | UUID v7 | User ↔ Company mapping | 003/021 |
|
|
||||||
| `regions` | SERIAL | States/Provinces/Prefectures | 004 |
|
|
||||||
| `cities` | SERIAL | Cities within regions | 004 |
|
|
||||||
| `jobs` | UUID v7 | Job postings | 005/021 |
|
|
||||||
| `applications` | UUID v7 | Job applications | 006/021 |
|
|
||||||
| `favorite_jobs` | SERIAL | Saved jobs by users | 007 |
|
|
||||||
| `password_resets` | SERIAL | Password reset tokens | 008 |
|
|
||||||
| `notifications` | UUID v7 | User notifications | 016 |
|
|
||||||
| `tickets` | UUID v7 | Support tickets | 017 |
|
|
||||||
| `ticket_messages` | UUID v7 | Ticket messages | 017 |
|
|
||||||
| `job_payments` | UUID v7 | Payment records | 019 |
|
|
||||||
| `job_posting_prices` | SERIAL | Pricing configuration | 019 |
|
|
||||||
| `login_audits` | SERIAL | Login audit trail | 013 |
|
|
||||||
| `activity_logs` | SERIAL | Activity logging | 013 |
|
|
||||||
|
|
||||||
---
|
### 2. Multi-Role Profiles
|
||||||
|
The system handles permissions through the `role` enum in the `users` table:
|
||||||
|
* `superadmin`: Global control over GoHorseJobs.
|
||||||
|
* `admin`: Can moderate companies/jobs inside the Backoffice.
|
||||||
|
* `recruiter`: A user attached to a `company_id` via `user_companies` who can post `jobs`.
|
||||||
|
* `candidate`: A standard user seeking jobs and making `applications`.
|
||||||
|
|
||||||
## 📋 Core Tables
|
A single email can only correspond to one role globally. If a user needs another role, they must change it or use a different credential.
|
||||||
|
|
||||||
### `users`
|
### 3. Migrations
|
||||||
|
Migrations are written in pure SQL and stored in `backend/migrations/`.
|
||||||
|
They follow a strict sequential numbering index (e.g., `000_schema.sql`, `001_roles.sql`, etc). The backend runtime executes these on startup if the database is out of sync.
|
||||||
|
|
||||||
System users including Candidates, Recruiters, and Admins.
|
### 4. Background Seeding
|
||||||
|
Seeding (populating test data) is explicitly decoupled from migrations. It lives isolated in the Node.js `seeder-api`.
|
||||||
```sql
|
To reset your local database to a clean, populated state:
|
||||||
CREATE TABLE users (
|
```bash
|
||||||
id SERIAL PRIMARY KEY,
|
# Deletes everything, remigrates, and inserts base data (except 153k cities)
|
||||||
identifier VARCHAR(100) UNIQUE NOT NULL, -- Login (username/email)
|
cd seeder-api && npm run seed:lite
|
||||||
password_hash VARCHAR(255) NOT NULL,
|
|
||||||
role VARCHAR(20) NOT NULL, -- 'superadmin'|'admin'|'recruiter'|'candidate'
|
|
||||||
|
|
||||||
-- Profile
|
|
||||||
full_name VARCHAR(255),
|
|
||||||
email VARCHAR(255),
|
|
||||||
name VARCHAR(255),
|
|
||||||
phone VARCHAR(30),
|
|
||||||
|
|
||||||
-- Candidate Profile Fields (015)
|
|
||||||
city VARCHAR(100),
|
|
||||||
state VARCHAR(50),
|
|
||||||
title VARCHAR(100),
|
|
||||||
experience VARCHAR(100),
|
|
||||||
skills TEXT[],
|
|
||||||
bio TEXT,
|
|
||||||
objective TEXT,
|
|
||||||
|
|
||||||
-- Multi-tenancy (020)
|
|
||||||
tenant_id INT REFERENCES companies(id),
|
|
||||||
status VARCHAR(20) DEFAULT 'active',
|
|
||||||
|
|
||||||
-- Settings
|
|
||||||
language VARCHAR(5) DEFAULT 'en',
|
|
||||||
active BOOLEAN DEFAULT true,
|
|
||||||
|
|
||||||
-- Timestamps
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
last_login_at TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Roles:**
|
|
||||||
- `superadmin` - Platform administrator
|
|
||||||
- `admin` - Company administrator
|
|
||||||
- `recruiter` - Job poster/recruiter
|
|
||||||
- `candidate` - Candidate/job seeker
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `user_roles`
|
|
||||||
|
|
||||||
Additional roles per user (for multi-role support).
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE user_roles (
|
|
||||||
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
role VARCHAR(50) NOT NULL,
|
|
||||||
PRIMARY KEY (user_id, role)
|
|
||||||
);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### `companies`
|
## ⚠️ Security Notes
|
||||||
|
|
||||||
Employer organizations that post jobs.
|
### The `password_hash` & Pepper
|
||||||
|
Bcrypt hashes stored in the standard `users` table require a secret pepper (`PASSWORD_PEPPER`).
|
||||||
```sql
|
* The raw hash stored in the DB cannot be cracked immediately due to the pepper.
|
||||||
CREATE TABLE companies (
|
* The seeder relies on reading the `PASSWORD_PEPPER` from `.env` to forge the initial super-admin and test accounts hashes. **If you change the `.env` pepper, the entire DB of seeded passwords immediately invalidates.** You must re-run `npm run seed`.
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
slug VARCHAR(255) UNIQUE NOT NULL, -- URL-friendly name
|
|
||||||
type VARCHAR(50) DEFAULT 'company', -- 'company'|'agency'|'system'
|
|
||||||
document VARCHAR(100), -- CNPJ/Tax ID
|
|
||||||
|
|
||||||
-- Location
|
|
||||||
address TEXT,
|
|
||||||
region_id INT REFERENCES regions(id),
|
|
||||||
city_id INT REFERENCES cities(id),
|
|
||||||
|
|
||||||
-- Contact
|
|
||||||
phone VARCHAR(30),
|
|
||||||
email VARCHAR(255),
|
|
||||||
website VARCHAR(255),
|
|
||||||
|
|
||||||
-- Branding
|
|
||||||
logo_url TEXT,
|
|
||||||
description TEXT, -- JSON or plain text
|
|
||||||
|
|
||||||
-- Status
|
|
||||||
active BOOLEAN DEFAULT true,
|
|
||||||
verified BOOLEAN DEFAULT false,
|
|
||||||
|
|
||||||
-- Timestamps
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `jobs`
|
|
||||||
|
|
||||||
Job postings created by companies.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE jobs (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
company_id INT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
||||||
created_by INT NOT NULL REFERENCES users(id),
|
|
||||||
|
|
||||||
-- Job Details
|
|
||||||
title VARCHAR(255) NOT NULL,
|
|
||||||
description TEXT NOT NULL,
|
|
||||||
|
|
||||||
-- Salary
|
|
||||||
salary_min DECIMAL(12,2),
|
|
||||||
salary_max DECIMAL(12,2),
|
|
||||||
salary_type VARCHAR(20), -- 'hourly'|'daily'|'weekly'|'monthly'|'yearly'
|
|
||||||
currency VARCHAR(3), -- 'BRL'|'USD'|'EUR'|'GBP'|'JPY'
|
|
||||||
|
|
||||||
-- Employment
|
|
||||||
employment_type VARCHAR(30), -- 'full-time'|'part-time'|'contract'|'dispatch'|...
|
|
||||||
work_mode VARCHAR(10), -- 'onsite'|'hybrid'|'remote'
|
|
||||||
working_hours VARCHAR(100),
|
|
||||||
|
|
||||||
-- Location
|
|
||||||
location VARCHAR(255),
|
|
||||||
region_id INT REFERENCES regions(id),
|
|
||||||
city_id INT REFERENCES cities(id),
|
|
||||||
|
|
||||||
-- Requirements & Benefits
|
|
||||||
requirements JSONB, -- Array of skills/requirements
|
|
||||||
benefits JSONB, -- Array of benefits
|
|
||||||
|
|
||||||
-- Visa & Language
|
|
||||||
visa_support BOOLEAN DEFAULT false,
|
|
||||||
language_level VARCHAR(20), -- 'N5'|'N4'|'N3'|'N2'|'N1'|'beginner'|'none'
|
|
||||||
|
|
||||||
-- Status
|
|
||||||
status VARCHAR(20) DEFAULT 'open', -- 'draft'|'open'|'closed'|'published'|...
|
|
||||||
is_featured BOOLEAN DEFAULT false,
|
|
||||||
|
|
||||||
-- Timestamps
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Status Values:**
|
|
||||||
- `draft` - Not published yet
|
|
||||||
- `open` - Accepting applications
|
|
||||||
- `published` - Live and visible
|
|
||||||
- `paused` - Temporarily hidden
|
|
||||||
- `closed` - No longer accepting
|
|
||||||
- `expired` - Past expiration date
|
|
||||||
- `archived` - Archived by employer
|
|
||||||
- `reported` - Flagged for review
|
|
||||||
- `review` - Under admin review
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `applications`
|
|
||||||
|
|
||||||
Job applications from candidates.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE applications (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
job_id INT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
||||||
user_id INT REFERENCES users(id), -- Nullable for guest applications
|
|
||||||
|
|
||||||
-- Applicant Info
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
email VARCHAR(255) NOT NULL,
|
|
||||||
phone VARCHAR(30),
|
|
||||||
line_id VARCHAR(100),
|
|
||||||
whatsapp VARCHAR(30),
|
|
||||||
|
|
||||||
-- Application Content
|
|
||||||
message TEXT,
|
|
||||||
resume_url VARCHAR(500),
|
|
||||||
documents JSONB,
|
|
||||||
|
|
||||||
-- Status
|
|
||||||
status VARCHAR(20) DEFAULT 'pending', -- 'pending'|'reviewed'|'shortlisted'|'rejected'|'hired'
|
|
||||||
notes TEXT, -- Recruiter notes
|
|
||||||
|
|
||||||
-- Timestamps
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🌍 Geographic Tables
|
|
||||||
|
|
||||||
### `regions`
|
|
||||||
|
|
||||||
States, provinces, or prefectures.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE regions (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
country_code VARCHAR(2) NOT NULL, -- 'BR'|'US'|'JP'
|
|
||||||
code VARCHAR(10) NOT NULL -- 'SP'|'CA'|'13'
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Seeded Regions:**
|
|
||||||
- 🇧🇷 Brazil: São Paulo (SP), Rio de Janeiro (RJ), Minas Gerais (MG)
|
|
||||||
- 🇺🇸 USA: California (CA), New York (NY), Texas (TX)
|
|
||||||
- 🇯🇵 Japan: Tokyo (13), Osaka (27)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `cities`
|
|
||||||
|
|
||||||
Cities within regions.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE cities (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
region_id INT NOT NULL REFERENCES regions(id),
|
|
||||||
name VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💰 Payment Tables
|
|
||||||
|
|
||||||
### `job_payments` (UUID)
|
|
||||||
|
|
||||||
Payment records for job postings.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE job_payments (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
job_id INT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
||||||
user_id INT REFERENCES users(id) ON DELETE SET NULL,
|
|
||||||
|
|
||||||
-- Stripe
|
|
||||||
stripe_session_id VARCHAR(255),
|
|
||||||
stripe_payment_intent VARCHAR(255),
|
|
||||||
stripe_customer_id VARCHAR(255),
|
|
||||||
|
|
||||||
-- Amount
|
|
||||||
amount DECIMAL(12,2) NOT NULL,
|
|
||||||
currency VARCHAR(3) DEFAULT 'USD',
|
|
||||||
|
|
||||||
-- Status
|
|
||||||
status VARCHAR(20) DEFAULT 'pending', -- 'pending'|'completed'|'failed'|'refunded'|'expired'
|
|
||||||
|
|
||||||
-- Billing
|
|
||||||
billing_type VARCHAR(20), -- 'company'|'individual'
|
|
||||||
billing_name VARCHAR(255),
|
|
||||||
billing_email VARCHAR(255),
|
|
||||||
|
|
||||||
-- Job Details
|
|
||||||
duration_days INT DEFAULT 30,
|
|
||||||
is_featured BOOLEAN DEFAULT false,
|
|
||||||
|
|
||||||
-- Timestamps
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
paid_at TIMESTAMP,
|
|
||||||
expires_at TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `job_posting_prices`
|
|
||||||
|
|
||||||
Pricing configuration for job postings.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE job_posting_prices (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
price DECIMAL(12,2) NOT NULL,
|
|
||||||
currency VARCHAR(3) DEFAULT 'USD',
|
|
||||||
duration_days INT DEFAULT 30,
|
|
||||||
is_featured BOOLEAN DEFAULT false,
|
|
||||||
stripe_price_id VARCHAR(255),
|
|
||||||
active BOOLEAN DEFAULT true,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔔 Notification Tables
|
|
||||||
|
|
||||||
### `notifications` (UUID)
|
|
||||||
|
|
||||||
User notifications.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE notifications (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
type VARCHAR(50),
|
|
||||||
title VARCHAR(255),
|
|
||||||
message TEXT,
|
|
||||||
read BOOLEAN DEFAULT false,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎫 Support Tables
|
|
||||||
|
|
||||||
### `tickets` (UUID)
|
|
||||||
|
|
||||||
Support tickets.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE tickets (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
subject VARCHAR(255) NOT NULL,
|
|
||||||
status VARCHAR(20) DEFAULT 'open', -- 'open'|'in_progress'|'resolved'|'closed'
|
|
||||||
priority VARCHAR(10) DEFAULT 'medium', -- 'low'|'medium'|'high'|'urgent'
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `ticket_messages` (UUID)
|
|
||||||
|
|
||||||
Messages within tickets.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE ticket_messages (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
ticket_id UUID NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
|
|
||||||
user_id INT NOT NULL,
|
|
||||||
message TEXT NOT NULL,
|
|
||||||
is_staff BOOLEAN DEFAULT false,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Audit Tables
|
|
||||||
|
|
||||||
### `login_audits`
|
|
||||||
|
|
||||||
Login attempt tracking.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE login_audits (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id INT,
|
|
||||||
identifier VARCHAR(100),
|
|
||||||
success BOOLEAN,
|
|
||||||
ip_address VARCHAR(45),
|
|
||||||
user_agent TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `activity_logs`
|
|
||||||
|
|
||||||
General activity logging.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE activity_logs (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id INT,
|
|
||||||
entity_type VARCHAR(50),
|
|
||||||
entity_id INT,
|
|
||||||
action VARCHAR(50),
|
|
||||||
details JSONB,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 Junction Tables
|
|
||||||
|
|
||||||
### `user_companies`
|
|
||||||
|
|
||||||
Maps users to companies (N:M relationship).
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE user_companies (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
company_id INT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
|
||||||
role VARCHAR(30) DEFAULT 'recruiter', -- 'admin'|'recruiter'
|
|
||||||
permissions JSONB,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE(user_id, company_id)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### `favorite_jobs`
|
|
||||||
|
|
||||||
Saved jobs by users.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE favorite_jobs (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
||||||
job_id INT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
UNIQUE(user_id, job_id)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 Migrations History
|
|
||||||
|
|
||||||
| # | File | Description |
|
|
||||||
|---|------|-------------|
|
|
||||||
| 001 | `001_create_users_table.sql` | Core users table |
|
|
||||||
| 002 | `002_create_companies_table.sql` | Companies/employers |
|
|
||||||
| 003 | `003_create_user_companies_table.sql` | User ↔ Company mapping |
|
|
||||||
| 004 | `004_create_prefectures_cities_tables.sql` | Geographic data |
|
|
||||||
| 005 | `005_create_jobs_table.sql` | Job postings |
|
|
||||||
| 006 | `006_create_applications_table.sql` | Job applications |
|
|
||||||
| 007 | `007_create_favorite_jobs_table.sql` | Saved jobs |
|
|
||||||
| 008 | `008_create_password_resets_table.sql` | Password reset tokens |
|
|
||||||
| 010 | `010_seed_super_admin.sql` | Default superadmin |
|
|
||||||
| 011 | `011_add_is_featured_to_jobs.sql` | Featured jobs flag |
|
|
||||||
| 012 | `012_add_work_mode.sql` | Work mode column |
|
|
||||||
| 013 | `013_create_backoffice_tables.sql` | Audit tables |
|
|
||||||
| 014 | `014_update_job_status_constraint.sql` | Status enum update |
|
|
||||||
| 015 | `015_add_candidate_profile_fields.sql` | Candidate profile |
|
|
||||||
| 016 | `016_create_notifications_table.sql` | Notifications |
|
|
||||||
| 017 | `017_create_tickets_table.sql` | Support tickets |
|
|
||||||
| 018 | `018_add_currency_to_jobs.sql` | Currency support |
|
|
||||||
| 019 | `019_create_job_payments_table.sql` | Payment tracking |
|
|
||||||
| 020 | `020_unify_schema.sql` | Schema unification |
|
|
||||||
| 999 | `999_fix_gohorse_schema.sql` | Schema fixes |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ ID Strategy Notes
|
|
||||||
|
|
||||||
The database uses a **hybrid ID strategy**:
|
|
||||||
|
|
||||||
| Strategy | Tables | Rationale |
|
|
||||||
|----------|--------|-----------|
|
|
||||||
| **UUID v7** | users, companies, jobs, applications, notifications, tickets, job_payments | Time-ordered, distributed-friendly, sortable, scalable |
|
|
||||||
| **SERIAL (INT)** | regions, cities, job_posting_prices, job_tags | Reference/Static data, low volume |
|
|
||||||
|
|
||||||
### UUID v7 (RFC 9562)
|
|
||||||
|
|
||||||
Starting from migration `021_create_uuid_v7_function.sql`, the database uses **UUID v7** instead of UUID v4:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- UUID v7 is generated by uuid_generate_v7() function
|
|
||||||
SELECT uuid_generate_v7();
|
|
||||||
-- Returns: 019438a1-2b3c-7abc-8123-4567890abcdef
|
|
||||||
-- ^^^^^^^^ time component (sortable)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits of UUID v7:**
|
|
||||||
- ⏱️ Time-ordered (sortable by creation time)
|
|
||||||
- 🌐 Distributed-friendly (no coordination needed)
|
|
||||||
- 📊 Better index performance than UUID v4
|
|
||||||
- 🔒 Contains embedded timestamp
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 Security Notes
|
|
||||||
|
|
||||||
1. **Password Hashing:** BCrypt with optional `PASSWORD_PEPPER` environment variable
|
|
||||||
2. **Soft Deletes:** Not implemented (uses `CASCADE` for referential integrity)
|
|
||||||
3. **Row-Level Security:** Not implemented (uses application-level checks)
|
|
||||||
4. **Audit Trail:** `login_audits` and `activity_logs` for compliance
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Related Documentation
|
|
||||||
|
|
||||||
- [Backend README](../backend/README.md) - API documentation
|
|
||||||
- [Seeder README](../seeder-api/README.md) - Database seeding
|
|
||||||
- [Backoffice README](../backoffice/README.md) - Admin panel
|
|
||||||
|
|
|
||||||
706
docs/DEVOPS.md
706
docs/DEVOPS.md
|
|
@ -1,651 +1,119 @@
|
||||||
# DevOps - GoHorseJobs (Development Environment)
|
# ☁️ DevOps & Infrastructure - GoHorseJobs
|
||||||
|
|
||||||
Infraestrutura, CI/CD e deploy do projeto GoHorseJobs no servidor `apolo`.
|
This document maps out the comprehensive DevOps lifecycle, the server topologies, the container orchestrations, and CI/CD operations powering GoHorseJobs.
|
||||||
|
|
||||||
> **Last Updated:** 2026-02-18
|
|
||||||
> **Servers:** Apolo VPS (Podman), Redbull VPS (Coolify)
|
|
||||||
> **Tech Stack:** Podman, Systemd (Quadlet), Traefik, PostgreSQL, Coolify
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ☁️ Cloudflare DNS Zone
|
|
||||||
|
|
||||||
### Zone Info
|
|
||||||
|
|
||||||
| Property | Value |
|
|
||||||
|----------|-------|
|
|
||||||
| **Zone ID** | `5e7e9286849525abf7f30b451b7964ac` |
|
|
||||||
| **Domain** | gohorsejobs.com |
|
|
||||||
| **Account** | gohorsejobs |
|
|
||||||
| **Email** | yamamoto@rede5.com.br |
|
|
||||||
| **Plan** | Free Website |
|
|
||||||
| **Name Servers** | chuck.ns.cloudflare.com, fatima.ns.cloudflare.com |
|
|
||||||
|
|
||||||
### API Access
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Token location: ~/.ssh/cloudflare-token
|
|
||||||
export CF_AUTH_EMAIL="yamamoto@rede5.com.br"
|
|
||||||
export CF_AUTH_KEY="5dcfd89a9d4ec330dede0d4074a518f26818e"
|
|
||||||
|
|
||||||
# List zones
|
|
||||||
curl -s -H "X-Auth-Email: $CF_AUTH_EMAIL" -H "X-Auth-Key: $CF_AUTH_KEY" \
|
|
||||||
"https://api.cloudflare.com/client/v4/zones"
|
|
||||||
|
|
||||||
# List DNS records
|
|
||||||
curl -s -H "X-Auth-Email: $CF_AUTH_EMAIL" -H "X-Auth-Key: $CF_AUTH_KEY" \
|
|
||||||
"https://api.cloudflare.com/client/v4/zones/5e7e9286849525abf7f30b451b7964ac/dns_records"
|
|
||||||
|
|
||||||
# Purge cache
|
|
||||||
curl -s -X DELETE -H "X-Auth-Email: $CF_AUTH_EMAIL" -H "X-Auth-Key: $CF_AUTH_KEY" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"https://api.cloudflare.com/client/v4/zones/5e7e9286849525abf7f30b451b7964ac/purge_cache" \
|
|
||||||
-d '{"purge_everything":true}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Active DNS Records (gohorsejobs.com)
|
|
||||||
|
|
||||||
| Subdomain | Type | IP/Target | Proxied |
|
|
||||||
|-----------|------|------------|---------|
|
|
||||||
| dev.gohorsejobs.com | A | 38.19.201.52 | No |
|
|
||||||
| api.gohorsejobs.com | A | 86.48.29.139 | Yes |
|
|
||||||
| api-dev.gohorsejobs.com | A | 86.48.29.139 | Yes |
|
|
||||||
| api-local.gohorsejobs.com | A | 38.19.201.52 | No |
|
|
||||||
| b-local.gohorsejobs.com | A | 38.19.201.52 | No |
|
|
||||||
| s-local.gohorsejobs.com | A | 38.19.201.52 | No |
|
|
||||||
| coolify-dev.gohorsejobs.com | A | 185.194.141.70 | No |
|
|
||||||
| local.gohorsejobs.com | A | 185.194.141.70 | No |
|
|
||||||
| api-local.gohorsejobs.com | A | 185.194.141.70 | No |
|
|
||||||
| b-local.gohorsejobs.com | A | 185.194.141.70 | No |
|
|
||||||
| s-local.gohorsejobs.com | A | 185.194.141.70 | No |
|
|
||||||
| panel.gohorsejobs.com | A | Multiple (Load Balanced) | Yes |
|
|
||||||
| pipe.gohorsejobs.com | A | Multiple (Load Balanced) | Yes |
|
|
||||||
| alert.gohorsejobs.com | A | Multiple (Load Balanced) | Yes |
|
|
||||||
| task.gohorsejobs.com | A | Multiple (Load Balanced) | Yes |
|
|
||||||
| stats.gohorsejobs.com | A | Multiple (Load Balanced) | Yes |
|
|
||||||
| storage.gohorsejobs.com | A | Multiple (Load Balanced) | Yes |
|
|
||||||
| base.gohorsejobs.com | A | Multiple | No |
|
|
||||||
| reg.gohorsejobs.com | A | Multiple (Load Balanced) | Yes |
|
|
||||||
| gohorsejobs.com | CNAME | gohorsejobs.pages.dev | Yes |
|
|
||||||
| *.gohorsejobs.com | CNAME | 8a3f435b-f374-4268-90f7-5610f577c706.cfargotunnel.com | Yes |
|
|
||||||
| mail.gohorsejobs.com | CNAME | everest.mxrouting.net | No |
|
|
||||||
|
|
||||||
> Total: 190 DNS records (paginados)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ☁️ Coolify DEV Environment (Redbull)
|
|
||||||
|
|
||||||
Ambiente de desenvolvimento no Coolify para deploy automatizado via Git.
|
|
||||||
|
|
||||||
### Server Info
|
|
||||||
|
|
||||||
| Property | Value |
|
|
||||||
|----------|-------|
|
|
||||||
| **Host** | redbull (185.194.141.70) |
|
|
||||||
| **Coolify URL** | https://redbull.rede5.com.br |
|
|
||||||
| **API Token** | `~/.ssh/coolify-redbull-token` |
|
|
||||||
| **SSH Key** | `~/.ssh/civo` |
|
|
||||||
| **Project UUID** | `gkgksco0ow4kgwo8ow4cgs8c` |
|
|
||||||
| **Environment** | `dev` |
|
|
||||||
|
|
||||||
### Resources Created
|
|
||||||
|
|
||||||
| Resource | UUID | Port | Domain | Status |
|
|
||||||
|----------|------|------|--------|--------|
|
|
||||||
| Backend | `iw4sow8s0kkg4cccsk08gsoo` | 8521 | https://api-local.gohorsejobs.com | running |
|
|
||||||
| Frontend | `ao8g40scws0w4cgo8coc8o40` | 3000 | https://local.gohorsejobs.com | running |
|
|
||||||
| Backoffice | `hg48wkw4wggwsswcwc8sooo4` | 3001 | https://b-local.gohorsejobs.com | running |
|
|
||||||
| Seeder | `q4w48gos8cgssso00o8w8gck` | 8080 | https://s-local.gohorsejobs.com | running:healthy |
|
|
||||||
| Database (PostgreSQL) | `bgws48os8wgwk08o48wg8k80` | 5432 | Internal only | running:healthy |
|
|
||||||
|
|
||||||
### API Reference
|
|
||||||
|
|
||||||
**Base URL:** `https://redbull.rede5.com.br/api/v1`
|
|
||||||
|
|
||||||
**Server UUID:** `m844o4gkwkwcc0k48swgs8c8`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Listar aplicações
|
|
||||||
curl -s -H "Authorization: Bearer $(cat ~/.ssh/coolify-redbull-token)" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/applications"
|
|
||||||
|
|
||||||
# Atualizar domínios (requer http:// ou https://)
|
|
||||||
curl -s -X PATCH -H "Authorization: Bearer $(cat ~/.ssh/coolify-redbull-token)" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/applications/<UUID>" \
|
|
||||||
-d '{"domains":"http://local.gohorsejobs.com","instant_deploy":true}'
|
|
||||||
|
|
||||||
# Deploy aplicação
|
|
||||||
curl -s -H "Authorization: Bearer $(cat ~/.ssh/coolify-redbull-token)" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/deploy?uuid=<UUID>"
|
|
||||||
|
|
||||||
# Ver domínios do servidor
|
|
||||||
curl -s -H "Authorization: Bearer $(cat ~/.ssh/coolify-redbull-token)" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/servers/m844o4gkwkwcc0k48swgs8c8/domains"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
GitHub (rede5/gohorsejobs.git) ←→ Forgejo (pipe.gohorsejobs.com)
|
|
||||||
│ │
|
|
||||||
▼ ▼
|
|
||||||
Coolify (redbull.rede5.com.br)
|
|
||||||
├── Traefik (reverse proxy + TLS via Let's Encrypt)
|
|
||||||
│
|
|
||||||
├── gohorsejobs-backend-dev → https://api-local.gohorsejobs.com
|
|
||||||
├── gohorsejobs-frontend-dev → https://local.gohorsejobs.com
|
|
||||||
├── gohorsejobs-backoffice-dev → https://b-local.gohorsejobs.com
|
|
||||||
├── gohorsejobs-seeder-dev → https://s-local.gohorsejobs.com
|
|
||||||
│
|
|
||||||
└── PostgreSQL 16 (gohorsejobs-dev) → Internal network only
|
|
||||||
```
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
Configured via Coolify UI or API:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DATABASE_URL=postgres://gohorsejobs:gohorsejobs123@bgws48os8wgwk08o48wg8k80:5432/gohorsejobs?sslmode=disable
|
|
||||||
BACKEND_PORT=8521
|
|
||||||
ENV=development
|
|
||||||
JWT_SECRET=gohorsejobs-dev-jwt-secret-2024-very-secure-key-32ch
|
|
||||||
JWT_EXPIRATION=7d
|
|
||||||
PASSWORD_PEPPER=gohorse-pepper
|
|
||||||
COOKIE_SECRET=gohorsejobs-cookie-secret-dev
|
|
||||||
CORS_ORIGINS=https://local.gohorsejobs.com,https://b-local.gohorsejobs.com,http://localhost:3000,http://localhost:8521
|
|
||||||
```
|
|
||||||
|
|
||||||
> ⚠️ **`PASSWORD_PEPPER` é crítico.** Todas as migration seeds e o seeder-api usam `gohorse-pepper`.
|
|
||||||
> Se este valor for alterado no Coolify sem regravar os hashes no banco, **todos os logins falharão**
|
|
||||||
> com `invalid credentials`. Veja a seção de troubleshooting abaixo.
|
|
||||||
|
|
||||||
### ⚠️ Troubleshooting: Login retorna `invalid credentials`
|
|
||||||
|
|
||||||
**Causa:** O `PASSWORD_PEPPER` no Coolify não coincide com o pepper usado para gerar os hashes no banco.
|
|
||||||
|
|
||||||
**Diagnóstico via SSH:**
|
|
||||||
```bash
|
|
||||||
ssh redbull
|
|
||||||
# Verificar pepper no container em execução:
|
|
||||||
docker inspect <backend_container_name> --format '{{range .Config.Env}}{{println .}}{{end}}' | grep PEPPER
|
|
||||||
|
|
||||||
# Testar login direto (sem escaping de shell):
|
|
||||||
cat > /tmp/login.json <<'EOF'
|
|
||||||
{"email":"lol","password":"Admin@2025!"}
|
|
||||||
EOF
|
|
||||||
docker run --rm --network coolify -v /tmp/login.json:/tmp/login.json \
|
|
||||||
curlimages/curl:latest -s -X POST \
|
|
||||||
http://<backend_container>:8521/api/v1/auth/login \
|
|
||||||
-H 'Content-Type: application/json' -d @/tmp/login.json
|
|
||||||
```
|
|
||||||
|
|
||||||
**Fix — opção 1 (preferível): corrigir o pepper no Coolify e re-rodar o seeder:**
|
|
||||||
```bash
|
|
||||||
TOKEN=$(cat ~/.ssh/coolify-redbull-token)
|
|
||||||
|
|
||||||
# 1. Atualizar PASSWORD_PEPPER
|
|
||||||
curl -s -X PATCH \
|
|
||||||
-H "Authorization: Bearer $TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"key":"PASSWORD_PEPPER","value":"gohorse-pepper"}' \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/applications/iw4sow8s0kkg4cccsk08gsoo/envs"
|
|
||||||
|
|
||||||
# 2. Reiniciar o backend (para ele pegar o novo pepper)
|
|
||||||
curl -s -H "Authorization: Bearer $TOKEN" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/applications/iw4sow8s0kkg4cccsk08gsoo/restart"
|
|
||||||
|
|
||||||
# 3. Re-rodar o seeder (ele regrava o hash com o pepper correto automaticamente)
|
|
||||||
curl -s -H "Authorization: Bearer $TOKEN" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/deploy?uuid=q4w48gos8cgssso00o8w8gck"
|
|
||||||
```
|
|
||||||
|
|
||||||
> O seeder (`seedUsers()`) sempre faz upsert do superadmin/lol com `bcrypt(senha + PEPPER)`.
|
|
||||||
> Mudar o pepper e re-rodar o seeder é suficiente — nenhuma migration precisa ser tocada.
|
|
||||||
|
|
||||||
**Fix — opção 2 (emergência, sem seeder): regravar hash direto no banco:**
|
|
||||||
```bash
|
|
||||||
ssh redbull
|
|
||||||
|
|
||||||
# Gerar novo hash com node (usando arquivo para evitar expansão de $ pelo shell):
|
|
||||||
mkdir -p /tmp/hashgen && cat > /tmp/hashgen/gen.js <<'EOF'
|
|
||||||
const b = require("./node_modules/bcryptjs");
|
|
||||||
console.log(b.hashSync("Admin@2025!" + process.env.PEPPER, 10));
|
|
||||||
EOF
|
|
||||||
docker run --rm -v /tmp/hashgen:/app -w /app -e PEPPER=gohorse-pepper \
|
|
||||||
node:20-alpine sh -c "npm install bcryptjs -s && node gen.js"
|
|
||||||
|
|
||||||
# Aplicar no banco (SEMPRE usar -f, nunca -c, para preservar os $ do hash):
|
|
||||||
cat > /tmp/fix_hash.sql <<'EOF'
|
|
||||||
UPDATE users SET password_hash = '<hash_gerado_acima>', status = 'active'
|
|
||||||
WHERE identifier IN ('lol', 'superadmin');
|
|
||||||
EOF
|
|
||||||
docker cp /tmp/fix_hash.sql bgws48os8wgwk08o48wg8k80:/tmp/fix_hash.sql
|
|
||||||
docker exec bgws48os8wgwk08o48wg8k80 psql -U gohorsejobs -d gohorsejobs -f /tmp/fix_hash.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
> ⚠️ **Nunca passe hash bcrypt via `-c '...'`** na linha de comando — o shell expande os `$`
|
|
||||||
> e corrompe o hash silenciosamente. Use sempre um arquivo e `-f`.
|
|
||||||
|
|
||||||
### Deploy via API
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Deploy application
|
|
||||||
curl -H "Authorization: Bearer $(cat ~/.ssh/coolify-redbull-token)" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/deploy?uuid=iw4sow8s0kkg4cccsk08gsoo"
|
|
||||||
|
|
||||||
# Check deployment status
|
|
||||||
curl -H "Authorization: Bearer $(cat ~/.ssh/coolify-redbull-token)" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/deployments/<deployment_uuid>"
|
|
||||||
|
|
||||||
# List applications
|
|
||||||
curl -H "Authorization: Bearer $(cat ~/.ssh/coolify-redbull-token)" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/applications"
|
|
||||||
|
|
||||||
# List databases
|
|
||||||
curl -H "Authorization: Bearer $(cat ~/.ssh/coolify-redbull-token)" \
|
|
||||||
"https://redbull.rede5.com.br/api/v1/databases"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Coolify Reference
|
|
||||||
|
|
||||||
- **Docs:** https://coolify.io/docs/get-started/introduction
|
|
||||||
- **API Reference:** https://coolify.io/docs/api-reference/authorization
|
|
||||||
- **GitHub Integration:** Uses SSH deploy key for private repo access
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🏗️ Architecture Diagrams
|
## 🏗️ Architecture Diagrams
|
||||||
|
|
||||||
### Full Infrastructure Overview
|
### 1. Global Infrastructure Overview (DEV / HML Environments)
|
||||||
|
A look into how our development environment handles requests through Cloudflare down to the Coolify-managed `Redbull` VPS.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TB
|
graph TB
|
||||||
subgraph Clients ["Clients"]
|
subgraph Clients ["Public Clients"]
|
||||||
Browser["Browser / Mobile"]
|
Browser["Web Browser / Mobile App"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph CF ["Cloudflare (DNS + CDN)"]
|
subgraph CF ["Cloudflare (DNS + Proxy + CDN)"]
|
||||||
DNS["DNS Zone: gohorsejobs.com"]
|
DNS["DNS Zone: gohorsejobs.com"]
|
||||||
|
WAF["Web Application Firewall (WAF)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Redbull ["Redbull VPS (185.194.141.70) — Coolify DEV"]
|
subgraph Redbull ["Redbull VPS (185.194.141.70) — Ubuntu"]
|
||||||
TraefikR("Traefik + Let's Encrypt")
|
TraefikR("Traefik (Reverse Proxy + Let's Encrypt)")
|
||||||
|
|
||||||
subgraph CoolifyApps ["Coolify Applications"]
|
subgraph CoolifyApps ["Coolify Application Containers"]
|
||||||
FE_C["Frontend (:3000)"]
|
FE_C["Frontend (Next.js)"]
|
||||||
BE_C["Backend API (:8521)"]
|
BE_C["Backend API (Go)"]
|
||||||
BO_C["Backoffice (:3001)"]
|
BO_C["Backoffice (NestJS)"]
|
||||||
SE_C["Seeder API (:8080)"]
|
SE_C["Seeder API (Node.js)"]
|
||||||
end
|
end
|
||||||
|
|
||||||
PG_C[("PostgreSQL 16\ngohorsejobs-dev")]
|
subgraph CoolifyData ["Coolify Data Containers"]
|
||||||
end
|
PG_C[("PostgreSQL 16")]
|
||||||
|
MQ_C["LavinMQ / RabbitMQ"]
|
||||||
subgraph Apolo ["Apolo VPS (38.19.201.52) — Podman/Quadlet"]
|
Redis_C[("Redis (Caching/Sessions)")]
|
||||||
TraefikA("Traefik")
|
|
||||||
|
|
||||||
subgraph PodmanApps ["Podman Containers (Systemd/Quadlet)"]
|
|
||||||
FE_A["Frontend (:3000)"]
|
|
||||||
BE_A["Backend API (:8521)"]
|
|
||||||
BO_A["Backoffice (:3001)"]
|
|
||||||
SE_A["Seeder API (:8080)"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
PG_A[("PostgreSQL\npostgres-main")]
|
|
||||||
Storage["/mnt/data\n(configs + DB data)"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph Git ["Git Repositories"]
|
Browser -->|HTTPS| DNS
|
||||||
GH["GitHub\nrede5/gohorsejobs"]
|
DNS --> WAF
|
||||||
FJ["Forgejo (pipe)\npipe.gohorsejobs.com"]
|
WAF -->|Proxy/Cache| TraefikR
|
||||||
end
|
TraefikR -->|local.gohorsejobs.com| FE_C
|
||||||
|
TraefikR -->|api-local.gohorsejobs.com| BE_C
|
||||||
|
TraefikR -->|b-local.gohorsejobs.com| BO_C
|
||||||
|
|
||||||
subgraph External ["External Services"]
|
FE_C -.->|REST /api/v1| BE_C
|
||||||
Stripe["Stripe (Payments)"]
|
BO_C -.->|Queries| PG_C
|
||||||
Firebase["Firebase (FCM)"]
|
BE_C <-->|Queries| PG_C
|
||||||
R2["Cloudflare R2 (Storage)"]
|
BE_C -.->|AMQP Pub| MQ_C
|
||||||
LavinMQ["LavinMQ (AMQP)"]
|
MQ_C -.->|AMQP Sub| BO_C
|
||||||
Resend["Resend (Email)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
%% Client Flow
|
|
||||||
Browser --> DNS
|
|
||||||
DNS -- "local.gohorsejobs.com" --> TraefikR
|
|
||||||
DNS -- "dev.gohorsejobs.com" --> TraefikA
|
|
||||||
|
|
||||||
%% Redbull Routing
|
|
||||||
TraefikR -- "local.gohorsejobs.com" --> FE_C
|
|
||||||
TraefikR -- "api-local.gohorsejobs.com" --> BE_C
|
|
||||||
TraefikR -- "b-local.gohorsejobs.com" --> BO_C
|
|
||||||
TraefikR -- "s-local.gohorsejobs.com" --> SE_C
|
|
||||||
BE_C --> PG_C
|
|
||||||
BO_C --> PG_C
|
|
||||||
SE_C --> PG_C
|
|
||||||
|
|
||||||
%% Apolo Routing
|
|
||||||
TraefikA -- "dev.gohorsejobs.com" --> FE_A
|
|
||||||
TraefikA -- "api-tmp.gohorsejobs.com" --> BE_A
|
|
||||||
TraefikA -- "b-tmp.gohorsejobs.com" --> BO_A
|
|
||||||
BE_A --> PG_A
|
|
||||||
BO_A --> PG_A
|
|
||||||
SE_A --> PG_A
|
|
||||||
PG_A -.-> Storage
|
|
||||||
|
|
||||||
%% Git Flow
|
|
||||||
GH <--> FJ
|
|
||||||
|
|
||||||
%% External
|
|
||||||
BE_C -.-> Stripe
|
|
||||||
BE_C -.-> Firebase
|
|
||||||
BE_C -.-> R2
|
|
||||||
BO_C -.-> LavinMQ
|
|
||||||
BO_C -.-> Resend
|
|
||||||
|
|
||||||
style PG_C fill:#336791,stroke:#fff,color:#fff
|
|
||||||
style PG_A fill:#336791,stroke:#fff,color:#fff
|
|
||||||
style TraefikR fill:#f5a623,stroke:#fff,color:#fff
|
|
||||||
style TraefikA fill:#f5a623,stroke:#fff,color:#fff
|
|
||||||
style CF fill:#f48120,stroke:#fff,color:#fff
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Apolo VPS (Podman/Quadlet) Detail
|
### 2. CI/CD Operations (Forgejo -> VPS)
|
||||||
|
How code travels from a `git push` on `dev` to the live container.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
sequenceDiagram
|
||||||
subgraph Host ["Apolo VPS (Host)"]
|
participant Dev as Developer
|
||||||
|
participant Git as GitHub (Origin)
|
||||||
subgraph FS ["File System (/mnt/data)"]
|
participant Forgejo as Pipe / Forgejo (CI)
|
||||||
EnvBE["/gohorsejobs/backend/.env"]
|
participant Coolify as Coolify Webhook
|
||||||
EnvBO["/gohorsejobs/backoffice/.env"]
|
participant VPS as Redbull (VPS)
|
||||||
EnvSE["/gohorsejobs/seeder-api/.env"]
|
|
||||||
DBData[("postgres-general")]
|
Dev->>Git: git push origin dev
|
||||||
end
|
Dev->>Forgejo: git push pipe dev
|
||||||
|
Note over Forgejo: Trigger Action (.forgejo/workflows/)
|
||||||
subgraph Net ["Network: web_proxy"]
|
Forgejo->>Coolify: POST Deploy Webhook
|
||||||
Traefik("Traefik")
|
Coolify-->>VPS: Fetch latest dev branch
|
||||||
|
Note over VPS: Coolify builds Nixpacks/Dockerfile
|
||||||
subgraph App ["Application Containers"]
|
Coolify-->>VPS: docker stop <old_containers>
|
||||||
BE["Backend API (:8521)"]
|
Coolify-->>VPS: docker run <new_containers>
|
||||||
BO["Backoffice (:3001)"]
|
Coolify-->>Forgejo: Deployment Success
|
||||||
SE["Seeder API (:8080)"]
|
Forgejo-->>Dev: Pipeline Green/Passed
|
||||||
FE["Frontend (:3000)"]
|
|
||||||
end
|
|
||||||
|
|
||||||
PG[("postgres-main (:5432)")]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
%% Ingress
|
|
||||||
Internet((Internet)) --> Traefik
|
|
||||||
|
|
||||||
%% Routing
|
|
||||||
Traefik -- "dev.gohorsejobs.com" --> FE
|
|
||||||
Traefik -- "api-tmp.gohorsejobs.com" --> BE
|
|
||||||
Traefik -- "b-tmp.gohorsejobs.com" --> BO
|
|
||||||
Traefik -- "seeder.gohorsejobs.com" --> SE
|
|
||||||
|
|
||||||
%% Config Mounts
|
|
||||||
EnvBE -.-> BE
|
|
||||||
EnvBO -.-> BO
|
|
||||||
EnvSE -.-> SE
|
|
||||||
|
|
||||||
%% Data Persistence
|
|
||||||
PG -.-> DBData
|
|
||||||
|
|
||||||
%% Database Connections
|
|
||||||
BE --> PG
|
|
||||||
BO --> PG
|
|
||||||
SE --> PG
|
|
||||||
|
|
||||||
style PG fill:#336791,stroke:#fff,color:#fff
|
|
||||||
style Traefik fill:#f5a623,stroke:#fff,color:#fff
|
|
||||||
```
|
|
||||||
|
|
||||||
### Coolify DEV (Redbull) Detail
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
subgraph Redbull ["Redbull VPS — Coolify (redbull.rede5.com.br)"]
|
|
||||||
Traefik("Traefik + Let's Encrypt")
|
|
||||||
|
|
||||||
subgraph Apps ["Applications (auto-deploy via Git)"]
|
|
||||||
BE["Backend Go\n:8521"]
|
|
||||||
FE["Frontend Next.js\n:3000"]
|
|
||||||
BO["Backoffice NestJS\n:3001"]
|
|
||||||
SE["Seeder API\n:8080"]
|
|
||||||
end
|
|
||||||
|
|
||||||
PG[("PostgreSQL 16\ngohorsejobs\n:5432")]
|
|
||||||
end
|
|
||||||
|
|
||||||
GH["GitHub (rede5/gohorsejobs)"] --> |"push dev"| Traefik
|
|
||||||
|
|
||||||
Internet((Internet)) --> Traefik
|
|
||||||
Traefik -- "api-local.gohorsejobs.com" --> BE
|
|
||||||
Traefik -- "local.gohorsejobs.com" --> FE
|
|
||||||
Traefik -- "b-local.gohorsejobs.com" --> BO
|
|
||||||
Traefik -- "s-local.gohorsejobs.com" --> SE
|
|
||||||
|
|
||||||
BE --> PG
|
|
||||||
BO --> PG
|
|
||||||
SE --> PG
|
|
||||||
|
|
||||||
style PG fill:#336791,stroke:#fff,color:#fff
|
|
||||||
style Traefik fill:#f5a623,stroke:#fff,color:#fff
|
|
||||||
```
|
|
||||||
|
|
||||||
### CI/CD Flow (Dual Pipeline)
|
|
||||||
|
|
||||||
Existem **2 pipelines independentes** disparados simultaneamente a cada push:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TD
|
|
||||||
Dev["Developer\ngit push dev"]
|
|
||||||
|
|
||||||
subgraph Pipeline1 ["Pipeline 1: GitHub → Coolify"]
|
|
||||||
GH["GitHub\n(origin)"]
|
|
||||||
Webhook["GitHub Webhook\n(push event)"]
|
|
||||||
Coolify["Coolify\n(redbull.rede5.com.br)"]
|
|
||||||
Redbull["Redbull VPS\nFrontend + Backend + Backoffice + Seeder"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Pipeline2 ["Pipeline 2: Forgejo → K3s Cluster"]
|
|
||||||
FJ["Forgejo\n(pipe.gohorsejobs.com)"]
|
|
||||||
Runner["Forgejo Actions Runner\n(self-hosted, K3s)"]
|
|
||||||
Registry["Container Registry\npipe.gohorsejobs.com"]
|
|
||||||
K3s["K3s Cluster\nBackend + Backoffice"]
|
|
||||||
end
|
|
||||||
|
|
||||||
Dev --> GH
|
|
||||||
Dev --> FJ
|
|
||||||
|
|
||||||
GH --> Webhook --> Coolify --> |"Docker build"| Redbull
|
|
||||||
|
|
||||||
FJ --> |"push triggers"| Runner
|
|
||||||
Runner --> |"docker build & push"| Registry
|
|
||||||
Runner --> |"kubectl apply"| K3s
|
|
||||||
```
|
|
||||||
|
|
||||||
| Pipeline | Trigger | Servicos | Destino |
|
|
||||||
|----------|---------|----------|---------|
|
|
||||||
| **GitHub → Coolify** | Webhook (push) | Frontend, Backend, Backoffice, Seeder | Redbull VPS (Docker) |
|
|
||||||
| **Forgejo → K3s** | Forgejo Actions (push) | Backend, Backoffice | K3s Cluster (Kubernetes) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 Forgejo CI/CD Pipeline (pipe.gohorsejobs.com)
|
|
||||||
|
|
||||||
O pipeline roda automaticamente via Forgejo Actions a cada push na branch `dev`.
|
|
||||||
|
|
||||||
### Workflow: `.forgejo/workflows/deploy.yaml`
|
|
||||||
|
|
||||||
| Job | Descricao | Status Atual |
|
|
||||||
|-----|-----------|-------------|
|
|
||||||
| **build-and-push** | Build Docker images (backend + backoffice), push to registry | OK |
|
|
||||||
| **deploy** | Deploy ao K3s via kubectl (requer KUBECONFIG secret) | OK (fix: KUBE_CONFIG → KUBECONFIG) |
|
|
||||||
|
|
||||||
### Pipeline Steps
|
|
||||||
|
|
||||||
1. **build-and-push** (OK):
|
|
||||||
- Checkout code
|
|
||||||
- Docker login no registry `pipe.gohorsejobs.com`
|
|
||||||
- Build & push backend: `pipe.gohorsejobs.com/bohessefm/gohorsejobs:latest`
|
|
||||||
- Build & push backoffice: `pipe.gohorsejobs.com/bohessefm/backoffice:latest`
|
|
||||||
|
|
||||||
2. **deploy** (FAIL - K3s nao configurado):
|
|
||||||
- Install kubectl
|
|
||||||
- Configure kubeconfig (via `secrets.KUBE_CONFIG`)
|
|
||||||
- Sync secrets e vars ao namespace `gohorsejobsdev`
|
|
||||||
- `kubectl apply -f k8s/dev/`
|
|
||||||
- Set image com SHA do commit
|
|
||||||
- Rollout restart deployments
|
|
||||||
|
|
||||||
> **Nota:** O job deploy usava `secrets.KUBE_CONFIG` mas o secret se chama `KUBECONFIG`. Corrigido no commit atual.
|
|
||||||
|
|
||||||
### Forgejo Actions Secrets & Variables
|
|
||||||
|
|
||||||
**Secrets** (configurados em Settings > Actions > Secrets):
|
|
||||||
- `FORGEJO_TOKEN` — Login no container registry
|
|
||||||
- `KUBECONFIG` — Kubeconfig para acesso ao K3s cluster
|
|
||||||
|
|
||||||
**Variables** (configurados em Settings > Actions > Variables):
|
|
||||||
- `DATABASE_URL`, `JWT_SECRET`, `PASSWORD_PEPPER`, `COOKIE_SECRET`, `COOKIE_DOMAIN`
|
|
||||||
- `BACKEND_PORT`, `BACKEND_HOST`, `ENV`, `CORS_ORIGINS`, `MTU`
|
|
||||||
- `AMQP_URL`, `S3_BUCKET`, `AWS_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_ENDPOINT`
|
|
||||||
- `RSA_PRIVATE_KEY_BASE64`, `JWT_EXPIRATION`
|
|
||||||
|
|
||||||
### Forgejo API
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Token location: ~/.ssh/forgejo-token
|
|
||||||
FORGEJO_TOKEN="03d23c54672519c8473bd9c46ae7820b13c8b287"
|
|
||||||
|
|
||||||
# Listar runs do pipeline
|
|
||||||
curl -s -H "Authorization: token $FORGEJO_TOKEN" \
|
|
||||||
"https://pipe.gohorsejobs.com/api/v1/repos/bohessefm/gohorsejobs/actions/tasks?limit=5"
|
|
||||||
|
|
||||||
# Listar repositorios
|
|
||||||
curl -s -H "Authorization: token $FORGEJO_TOKEN" \
|
|
||||||
"https://pipe.gohorsejobs.com/api/v1/user/repos"
|
|
||||||
```
|
|
||||||
|
|
||||||
### GitHub Webhooks (Auto-deploy Coolify)
|
|
||||||
|
|
||||||
Webhooks configurados no GitHub apontando para o Coolify:
|
|
||||||
|
|
||||||
| App | Webhook URL |
|
|
||||||
|-----|-------------|
|
|
||||||
| Backend | `https://redbull.rede5.com.br/webhooks/source/github/events/manual?uuid=iw4sow8s0kkg4cccsk08gsoo&secret=...` |
|
|
||||||
| Frontend | `https://redbull.rede5.com.br/webhooks/source/github/events/manual?uuid=ao8g40scws0w4cgo8coc8o40&secret=...` |
|
|
||||||
| Backoffice | `https://redbull.rede5.com.br/webhooks/source/github/events/manual?uuid=hg48wkw4wggwsswcwc8sooo4&secret=...` |
|
|
||||||
| Seeder | `https://redbull.rede5.com.br/webhooks/source/github/events/manual?uuid=q4w48gos8cgssso00o8w8gck&secret=...` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔑 Credenciais e Tokens (Referencias)
|
|
||||||
|
|
||||||
Todos os tokens estao armazenados em `~/.ssh/`:
|
|
||||||
|
|
||||||
| Arquivo | Servico | Uso |
|
|
||||||
|---------|---------|-----|
|
|
||||||
| `~/.ssh/coolify-redbull-token` | Coolify API | Deploy e gerenciamento de apps |
|
|
||||||
| `~/.ssh/forgejo-token` | Forgejo API (pipe) | CI/CD, webhooks, repos |
|
|
||||||
| `~/.ssh/github-token` | GitHub API | Webhooks, repos |
|
|
||||||
| `~/.ssh/cloudflare-token` | Cloudflare API | DNS, cache |
|
|
||||||
| `~/.ssh/absam-token` | Absam Cloud API | VPS management |
|
|
||||||
| `~/.ssh/forgejo-gohorsejobs` | SSH Key | Forgejo Git operations |
|
|
||||||
| `~/.ssh/civo` | SSH Key | Acesso VPS Redbull |
|
|
||||||
| `~/.ssh/github` | SSH Key | GitHub Git operations |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💾 Storage & Persistence (`/mnt/data`)
|
|
||||||
|
|
||||||
All persistent data and configuration files are stored in `/mnt/data` on the host.
|
|
||||||
|
|
||||||
| Host Path | Container Path | Purpose | Type |
|
|
||||||
|-----------|----------------|---------|------|
|
|
||||||
| `/mnt/data/gohorsejobs/backend/.env` | (Injected Env) | **Backend Config:** Secrets, DB URL, Port settings. | File |
|
|
||||||
| `/mnt/data/gohorsejobs/backoffice/.env` | (Injected Env) | **Backoffice Config:** Secrets, DB URL. | File |
|
|
||||||
| `/mnt/data/gohorsejobs/seeder-api/.env` | (Injected Env) | **Seeder Config:** Secrets, DB URL. | File |
|
|
||||||
| `/mnt/data/postgres-general` | `/var/lib/postgresql/data` | **Database Storage:** Main storage for `postgres-main` container. Contains `gohorsejobs_dev` DB. | Directory |
|
|
||||||
|
|
||||||
> **Backup Note:** To backup the environment, ensure `/mnt/data/gohorsejobs` and `/mnt/data/postgres-general` are included in snapshots.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🌍 Service Maps & Networking
|
|
||||||
|
|
||||||
### 🚦 Traefik Routing
|
|
||||||
Services are exposed via Traefik labels defined in the Quadlet `.container` files.
|
|
||||||
|
|
||||||
| Domain | Service | Internal Port | Host Port (Debug) |
|
|
||||||
|--------|---------|---------------|-------------------|
|
|
||||||
| `dev.gohorsejobs.com` | `gohorsejobs-frontend-dev` | `3000` | `8523` |
|
|
||||||
| `api-tmp.gohorsejobs.com` | `gohorsejobs-backend-dev` | `8521` | `8521` |
|
|
||||||
| `b-tmp.gohorsejobs.com` | `gohorsejobs-backoffice-dev` | `3001` | - |
|
|
||||||
| `seeder.gohorsejobs.com` | `gohorsejobs-seeder-dev` | `8080` | `8522` |
|
|
||||||
|
|
||||||
### 🛑 Security
|
|
||||||
- **Backend/Seeder/Frontend** expose ports to the Host (`85xx`) for debugging/direct access if needed.
|
|
||||||
- **Backoffice** is *only* accessible via Traefik (internal network).
|
|
||||||
- **PostgreSQL** is *only* accessible internally via `web_proxy` network (no host port binding).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Operational Guide
|
|
||||||
|
|
||||||
### 1. View & Manage Configs
|
|
||||||
Configurations are **not** inside containers. Edit them on the host:
|
|
||||||
```bash
|
|
||||||
# Edit Backend Config
|
|
||||||
vim /mnt/data/gohorsejobs/backend/.env
|
|
||||||
|
|
||||||
# Apply changes
|
|
||||||
systemctl restart gohorsejobs-backend-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Full Environment Restart
|
|
||||||
To restart all GoHorseJobs related services (excluding Database):
|
|
||||||
```bash
|
|
||||||
systemctl restart gohorsejobs-backend-dev gohorsejobs-backoffice-dev gohorsejobs-seeder-dev gohorsejobs-frontend-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Database Access
|
|
||||||
Access the local database directly via the `postgres-main` container:
|
|
||||||
```bash
|
|
||||||
# Internal Connection
|
|
||||||
docker exec -it postgres-main psql -U yuki -d gohorsejobs_dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Deployment Pipeline (Manual)
|
## 🛠️ Environments Topology
|
||||||
|
|
||||||
Current workflow uses **Local Build** -> **Forgejo Registry** -> **Server Pull**.
|
| Environment | Branch | Use Case | Server Host | Reverse Proxy | Config Manager | Domains |
|
||||||
|
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
||||||
|
| **Local (Host)** | N/A | Developer Sandbox | Laptop / PC | None | `start.sh` (Bare metal) | `localhost:8963` |
|
||||||
|
| **DEV** | `dev` | Continuous Integration | `Redbull` VPS | Traefik | Coolify v4 | `local.`, `api-local.`, `b-local.` |
|
||||||
|
| **HML** | `hml` | QA / Staging Testing | `Apolo` VPS | Nginx proxy | Podman (Quadlet) | `hml.`, `api-hml.`, `b-hml.` |
|
||||||
|
| **PRD** | `main` | Production Live | `Zeus`/`Poseidon` | Traefik Ingress | Kubernetes (K3s) | `gohorsejobs.com`, `api.` |
|
||||||
|
|
||||||
### 1. Build & Push (Local Machine)
|
---
|
||||||
|
|
||||||
|
## 🔧 Coolify Instance (Redbull)
|
||||||
|
|
||||||
|
The `dev` branch automatically mirrors to the Redbull server (185.194.141.70) managed by Coolify.
|
||||||
|
* **Coolify Interface:** `https://redbull.rede5.com.br`
|
||||||
|
* **GitHub Integration:** Relies on an SSH deployment key injected into the Forgejo actions.
|
||||||
|
|
||||||
|
### Container Rules
|
||||||
|
* Never manually `docker run` on Redbull. Use the Coolify interface to add environment variables or alter build commands.
|
||||||
|
* **Secrets:** Managed via Coolify Environment Variables. (e.g., `PASSWORD_PEPPER`, `JWT_SECRET`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Troubleshooting & Known Faultlines
|
||||||
|
|
||||||
|
### 1. `Invalid Credentials` Right After DB Seed
|
||||||
|
If the Backend Go server complains about invalid passwords right after you run `npm run seed`:
|
||||||
|
1. Check the `PASSWORD_PEPPER` inside the Coolify instance for the `seeder-api`.
|
||||||
|
2. It **must exactly match** the pepper configured for the Backend API.
|
||||||
|
3. If they matched, rerun `npm run seed` via the Coolify interface to force hash recalculation over the raw DB rows.
|
||||||
|
|
||||||
|
### 2. Out of Memory (OOMKilled) on Build
|
||||||
|
Node.js (for Next.js and NestJS) and Go can eat a lot of RAM during concurrent Coolify builds.
|
||||||
|
* **Fix:** Ensure the Server Actions inside Coolify are set to stagger deployments, preventing out-of-memory cascading crashes on Redbull.
|
||||||
|
|
||||||
|
### 3. SSH Connectivity
|
||||||
```bash
|
```bash
|
||||||
# Login
|
# Connecting to Redbull
|
||||||
podman login forgejo-gru.rede5.com.br
|
ssh root@185.194.141.70 -p 22
|
||||||
|
|
||||||
# Build
|
|
||||||
cd backend
|
|
||||||
podman build -t forgejo-gru.rede5.com.br/rede5/gohorsejobs-backend:latest .
|
|
||||||
|
|
||||||
# Push
|
|
||||||
podman push forgejo-gru.rede5.com.br/rede5/gohorsejobs-backend:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Deploy (On Apolo Server)
|
|
||||||
```bash
|
|
||||||
ssh root@apolo
|
|
||||||
|
|
||||||
# Pull new image
|
|
||||||
podman pull forgejo-gru.rede5.com.br/rede5/gohorsejobs-backend:latest
|
|
||||||
|
|
||||||
# Restart service (Systemd handles container recreation)
|
|
||||||
systemctl restart gohorsejobs-backend-dev
|
|
||||||
```
|
```
|
||||||
|
If access is denied, ensure your local public key is registered in `~/.ssh/authorized_keys` or injected via the VPS admin panel.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,48 @@
|
||||||
# Project Documentation - GoHorse Jobs
|
# 📚 Project Documentation - GoHorseJobs
|
||||||
|
|
||||||
This directory holds internal team documentation, guides, and extended context regarding project structure and architecture.
|
Welcome to the central documentation hub for **GoHorseJobs**. This repository contains the collective knowledge, architecture decisions, and operational guides for our B2B SaaS recruitment platform.
|
||||||
|
|
||||||
## 🚨 AI Rules Warning 🚨
|
---
|
||||||
Before making ANY changes to infrastructure deployment manifests or Kubernetes files, please refer to the project-wide AI Rules located at `.agent/rules.md`.
|
|
||||||
**Do NOT touch `k3s` or `k8s` files.** Do not alter raw RSA/base64 authentication keys.
|
## 🧭 Navigation & Index
|
||||||
|
|
||||||
|
Choose a specific domain below to dive deep into our technical implementation and guides.
|
||||||
|
|
||||||
|
### 🤖 1. AI & Developer Directives
|
||||||
|
* **[Context for AI Agents (AGENTS.md)](AGENTS.md)**: The supreme source of truth for Claude, Cursor, and other AI coding assistants. Contains stack rules, passwords, gotchas, and layout contexts.
|
||||||
|
* **[Rules (.agent/rules.md)](../.agent/rules.md)**: Absolute system boundaries and deployment limits.
|
||||||
|
|
||||||
|
### 🏗️ 2. High-Level Architecture
|
||||||
|
* **[DevOps & Infrastructure (DEVOPS.md)](DEVOPS.md)**: Full mapping of Cloudflare DNS, Traefik, VPS (Redbull/Apolo), Docker/Coolify containers, and CI/CD pipelines (Forgejo/Drone). Includes rich Mermaid diagrams.
|
||||||
|
* **[Database Schema (DATABASE.md)](DATABASE.md)**: PostgreSQL schemas, relationships, UUID v7 indexing strategies, and ERD visualizing the core data flow.
|
||||||
|
|
||||||
|
### 🔌 3. Application Interfaces (APIs)
|
||||||
|
* **[API Routes (API.md)](API.md)**: Endpoints mapped for the Go Backend (`/api/v1`), NestJS Backoffice services, and internal Node.js Seeder-API.
|
||||||
|
* **[API Security (API_SECURITY.md)](API_SECURITY.md)**: Details on HS256 JWT implementations, RBAC (Role-Based Access Control) levels, and CORS policies.
|
||||||
|
* **[AppSec Strategy (APPSEC_STRATEGY.md)](APPSEC_STRATEGY.md)**: The core mitigation plan against XSS, IDOR, Mass Assignment, and testing vectors within Next.js.
|
||||||
|
|
||||||
|
### 🫂 4. Operations & Testing
|
||||||
|
* **[Test Users & Data (TEST_USERS.md)](TEST_USERS.md)**: Comprehensive list of robust local credentials, passwords, dummy candidates, and seeded companies.
|
||||||
|
* **[Deployment Routes (WORKFLOWS.md)](WORKFLOWS.md)**: A catalog of our `.forgejo` and GitHub Actions synchronizations.
|
||||||
|
* **[Tasks (TASKS.md)](TASKS.md)**: Open checklist of internal improvements.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Launch (Dev)
|
||||||
|
|
||||||
|
If you are a new developer or setting up the environment post-clone, rely on our interactive script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/gohorsejobs
|
||||||
|
./start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options overview**:
|
||||||
|
* `1`: General Start (Frontend + Backend)
|
||||||
|
* `2`: Fresh Start (Reset Postgres Data -> Run Migrations -> Seed Core Data -> Start)
|
||||||
|
* `3`: God Mode (Frontend + Backend + Backoffice)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Final Notice
|
||||||
|
**Do NOT** alter base configuration settings (such as encryption secrets, or Kubernetes `k3s`/`k8s` manifests) unless explicitly guided by the Lead Engineer or following the strict protocols in `.agent/rules.md`.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,55 @@
|
||||||
# Test Users - GoHorseJobs
|
# 🧪 Test Users & Data Scenarios - GoHorseJobs
|
||||||
|
|
||||||
> **DEPRECATED / MOVED**
|
When running the platform locally (via `start.sh`) or on the DEV server (`local.gohorsejobs.com`), the `seeder-api` provisions a rich set of test accounts representing distinct personas.
|
||||||
> Este arquivo foi depreciado e toda a documentação das matrizes de Usuários de Teste foi movida e unificada diretamente no **`[README.md](../README.md)`** da raiz do projeto.
|
|
||||||
>
|
All accounts use the exact same password to simplify development.
|
||||||
> As rotas antigas do `superadmin` foram aposentadas via migrations da base, onde o perfil oficial master passa a se chamar unicamente: `lol`.
|
|
||||||
|
**Universal Test Password:** `Admin@2025!`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 Core Personas (The "Golden" Accounts)
|
||||||
|
|
||||||
|
These are the primary accounts you should use for daily development and E2E testing.
|
||||||
|
|
||||||
|
| Role | Identifier (Username) | Email | Purpose |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **Superadmin** | `lol` | `lol@gohorsejobs.com` | Complete system access. Bypass company walls. Manage features. |
|
||||||
|
| **Superadmin** | `superadmin` | `admin@gohorsejobs.com` | Secondary global admin. |
|
||||||
|
| **Admin** | `admin` | `moderator@gohorsejobs.com` | Reviews pending companies/jobs in backoffice. |
|
||||||
|
| **Recruiter** | `recruiter` | `hr@techcorp.com` | Posts jobs, manages applicants for "TechCorp" (Company ID: 1). |
|
||||||
|
| **Recruiter** | `jane_hr` | `jane.doe@startup.io` | Recruiter for a pending/unapproved company to test gating. |
|
||||||
|
| **Candidate** | `candidate` | `carlos.dev@gmail.com` | Standard job seeker. Pre-filled resume and skills. Has 3 active applications. |
|
||||||
|
| **Candidate** | `newbie` | `new.user@hotmail.com` | Fresh account with 0 applications and empty profile to test onboarding flows. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏢 Auto-Generated Companies & Jobs
|
||||||
|
|
||||||
|
The DB seeder simulates a healthy marketplace.
|
||||||
|
If you run `npm run seed:lite`, the DB receives:
|
||||||
|
* **50 Companies**: Ranging from "TechCorp" (Active) to "SuspiciousLTDA" (Pending/Rejected).
|
||||||
|
* **100 Jobs**: Distributed among the active companies. Various statuses (`published`, `draft`, `closed`).
|
||||||
|
* **200 Applications**: Dummy candidates applying to random jobs, in varied pipelines (`pending`, `reviewing`, `accepted`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Login & Auth "Gotchas"
|
||||||
|
|
||||||
|
### 1. `Invalid Credentials` Right After Fresh Seed
|
||||||
|
If you completely reset the DB using `start.sh` (Option 6) or manually clear Postgres, and your first login returns "Invalid credentials", **do not panic and do not manually change the DB hash**.
|
||||||
|
* **Cause**: The Node.js seeder calculates the bcrypt hash using an environment variable called `PASSWORD_PEPPER` (`gohorse-pepper`). If this variable was empty or mismatched with the Backend API, the hash is physically wrong.
|
||||||
|
* **Fix**: Ensure your `.env` files in both `backend` and `seeder-api` contain `PASSWORD_PEPPER=gohorse-pepper`. Then simply run `cd seeder-api && npm run seed` again to fix the hashes.
|
||||||
|
|
||||||
|
### 2. Login Payload Fields
|
||||||
|
The frontend sends:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "lol",
|
||||||
|
"password": "..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**Important:** Even though the frontend passes the username `lol`, the JSON key **must** be `"email"`. The Go backend natively handles resolving `"email"` against the `identifier` OR `email` database columns.
|
||||||
|
|
||||||
|
### 3. Account Status Flags
|
||||||
|
The `users` table has an `is_active` boolean. If a user is manually deactivated by an admin, the login endpoint will return a generic `401 Unauthorized` (or specific Account Locked error depending on the exact route version) to prevent user enumeration.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue