docs: complete project documentation overhaul

- Add comprehensive root README with badges, architecture diagram, and setup guide
- Update backend README with security middlewares and endpoint documentation
- Update frontend README with design system and page structure
- Update seeder-api README with generated data and credentials
- Add internal module READMEs (middleware, handlers, components)
- Document Clean Architecture layers and request flow
- Add environment variables reference table
This commit is contained in:
Tiago Yamamoto 2025-12-09 19:36:36 -03:00
parent 1c7ef95c1a
commit 7934afcf0d
22 changed files with 1578 additions and 362 deletions

322
README.md
View file

@ -1,78 +1,306 @@
# 🐴 GoHorse Jobs (Vagas para Tecnologia)
# 🐴 GoHorse Jobs
A comprehensive recruitment platform connecting job seekers with opportunities in the technology sector.
[![Go](https://img.shields.io/badge/Go-1.24-00ADD8?style=flat-square&logo=go)](https://golang.org/)
[![Next.js](https://img.shields.io/badge/Next.js-15-black?style=flat-square&logo=next.js)](https://nextjs.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-336791?style=flat-square&logo=postgresql)](https://postgresql.org/)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED?style=flat-square&logo=docker)](https://docker.com/)
[![License](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE)
## 📋 Project Overview
> 🇧🇷 Plataforma de recrutamento conectando profissionais de tecnologia a oportunidades de emprego.
GoHorse Jobs is a multi-service application designed to facilitate the hiring process. It consists of a high-performance Go backend, a modern Next.js frontend, and a specialized Seeder API for generating realistic test data.
---
## 🔐 Credentials (Test Environment)
## 📋 Índice
Use these credentials to access the different dashboards:
- [Visão Geral](#-visão-geral)
- [Arquitetura](#-arquitetura)
- [Tech Stack](#-tech-stack)
- [Pré-requisitos](#-pré-requisitos)
- [Instalação](#-instalação)
- [Variáveis de Ambiente](#-variáveis-de-ambiente)
- [Scripts Disponíveis](#-scripts-disponíveis)
- [Credenciais de Teste](#-credenciais-de-teste)
- [Documentação da API](#-documentação-da-api)
- [Estrutura de Pastas](#-estrutura-de-pastas)
| User Type | Identifier | Password | Dashboard |
|-----------|------------|----------|-----------|
| **SuperAdmin** | `superadmin` | `Admin@2025!` | `/dashboard/admin` (System Stats) |
| **Company Admin** | `takeshi_yamamoto` | `Takeshi@2025` | `/dashboard/empresa` (Manage Jobs) |
| **Recruiter** | `maria_santos` | `User@2025` | `/dashboard/empresa` (View Candidates) |
| **Candidate** | `paulo_santos` | `User@2025` | `/dashboard/candidato` (Apply for Jobs) |
---
## 🏗️ Architecture
## 🎯 Visão Geral
The project follows a microservices-inspired architecture:
**GoHorse Jobs** é uma plataforma completa de recrutamento que permite:
- 🏢 **Empresas**: Publicar vagas, gerenciar candidaturas e comunicar-se com candidatos
- 👤 **Candidatos**: Buscar vagas, candidatar-se e acompanhar status
- 👑 **Administradores**: Gerenciar todo o sistema com painel administrativo
---
## 🏗️ Arquitetura
O projeto segue uma arquitetura de **microserviços** com três componentes principais:
```mermaid
graph TD
User((User))
Frontend[Frontend (Next.js)]
Backend[Backend (Go/Gin)]
Seeder[Seeder API (Node.js)]
DB[(PostgreSQL)]
graph TB
subgraph Frontend
A[Next.js 15<br/>App Router]
end
User --> Frontend
Frontend -->|HTTP/REST| Backend
Seeder -->|Writes| DB
Backend -->|Reads/Writes| DB
subgraph Backend
B[Go API<br/>Clean Architecture]
end
subgraph Database
C[(PostgreSQL 16)]
end
subgraph Seeder
D[Node.js<br/>Seeder API]
end
A -->|REST API| B
B -->|GORM| C
D -->|pg client| C
```
## 🚀 Quick Start
### Padrões Arquiteturais
### 1. Run the Backend API
We have provided a convenience script to start the backend quickly.
| Componente | Padrão | Descrição |
|------------|--------|-----------|
| **Backend** | Clean Architecture | Separação em `handlers`, `services`, `models`, `usecases` |
| **Frontend** | App Router | Next.js 15 com Server Components e Client Components |
| **Multi-tenancy** | JWT + Company Context | Isolamento de dados por empresa via middleware |
---
## 🛠️ Tech Stack
### Backend
| Tecnologia | Versão | Uso |
|------------|--------|-----|
| Go | 1.24 | Linguagem principal |
| PostgreSQL | 16 | Banco de dados |
| JWT | v5 | Autenticação |
| Swagger | 2.0 | Documentação API |
### Frontend
| Tecnologia | Versão | Uso |
|------------|--------|-----|
| Next.js | 15 | Framework React |
| Tailwind CSS | 4 | Estilização |
| shadcn/ui | - | Componentes UI |
| Zod | 3.x | Validação de schemas |
| React Hook Form | 7.x | Gerenciamento de forms |
### DevOps
| Tecnologia | Uso |
|------------|-----|
| Docker | Containerização |
| Alpine Linux | Imagem base (mínima) |
---
## 📦 Pré-requisitos
Antes de começar, certifique-se de ter instalado:
- **Docker** (v24+) e **Docker Compose** (v2+)
- **Go** (v1.24+) - para desenvolvimento local
- **Node.js** (v20+) e **npm** - para frontend e seeder
- **PostgreSQL** (v16+) - se rodar sem Docker
---
## 🚀 Instalação
### Opção 1: Script de Desenvolvimento (Recomendado)
```bash
# Clone o repositório
git clone https://github.com/rede5/gohorsejobs.git
cd gohorsejobs
# Execute o script de desenvolvimento
./run_dev.sh
```
### 2. Run the Frontend
### Opção 2: Manual (Passo a Passo)
```bash
# 1. Clone o repositório
git clone https://github.com/rede5/gohorsejobs.git
cd gohorsejobs
# 2. Configure as variáveis de ambiente
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env
cp seeder-api/.env.example seeder-api/.env
# 3. Inicie o banco de dados (PostgreSQL)
# Certifique-se de ter um PostgreSQL rodando na porta 5432
# 4. Inicie o Backend
cd backend
go run ./cmd/api
# 5. Em outro terminal, inicie o Frontend
cd frontend
npm install
npm run dev
# 6. (Opcional) Popule o banco com dados de teste
cd seeder-api
npm install
npm run seed
```
### 3. Database Setup & Seeding
Refer to `seeder-api/README.md` for detailed instructions on populating the database.
---
## 📊 Status & Tasks
## 🔐 Variáveis de Ambiente
### Completed ✅
- [x] Backend API Structure
- [x] Docker Configuration
- [x] Frontend Dashboard (Company)
- [x] Seeder Logic (Users, Companies, Jobs)
- [x] Documentation Unification
- [x] Branding Update (GoHorse Jobs)
### Backend (`backend/.env`)
### In Progress 🚧
- [ ] Integration of complete Frontend-Backend flow
- [ ] Advanced Search Filters
- [ ] Real-time Notifications
| Variável | Descrição | Exemplo |
|----------|-----------|---------|
| `DB_HOST` | Host do PostgreSQL | `localhost` |
| `DB_PORT` | Porta do PostgreSQL | `5432` |
| `DB_USER` | Usuário do banco | `postgres` |
| `DB_PASSWORD` | Senha do banco | `yourpassword` |
| `DB_NAME` | Nome do banco | `gohorsejobs` |
| `JWT_SECRET` | Secret para JWT (min 32 chars) | `your-secret-key-at-least-32-chars` |
| `PORT` | Porta da API | `8080` |
| `ENV` | Ambiente | `development` ou `production` |
| `CORS_ORIGINS` | Origens permitidas | `http://localhost:3000` |
## 📚 Documentation
- [Backend Documentation](./backend/README.md)
- [Frontend Documentation](./frontend/README.md)
- [Seeder Documentation](./seeder-api/README.md)
### Frontend (`frontend/.env`)
| Variável | Descrição | Exemplo |
|----------|-----------|---------|
| `NEXT_PUBLIC_API_URL` | URL da API Backend | `http://localhost:8080` |
---
*Generated by Antigravity*
## 📜 Scripts Disponíveis
### Raiz do Projeto
| Comando | Descrição |
|---------|-----------|
| `./run_dev.sh` | Inicia backend + frontend em modo desenvolvimento |
| `./run_dev_seed.sh` | Inicia tudo + popula banco com dados de teste |
### Backend (`cd backend`)
| Comando | Descrição |
|---------|-----------|
| `go run ./cmd/api` | Inicia o servidor de desenvolvimento |
| `go build ./cmd/api` | Compila o binário |
| `go test ./...` | Executa todos os testes |
| `swag init -g cmd/api/main.go` | Regenera documentação Swagger |
### Frontend (`cd frontend`)
| Comando | Descrição |
|---------|-----------|
| `npm run dev` | Inicia servidor de desenvolvimento |
| `npm run build` | Compila para produção |
| `npm run start` | Inicia servidor de produção |
| `npm run lint` | Executa linter |
### Seeder API (`cd seeder-api`)
| Comando | Descrição |
|---------|-----------|
| `npm run seed` | Popula banco com todos os dados |
| `npm run seed:reset` | Limpa banco e repopula |
| `npm run seed:users` | Popula apenas usuários |
---
## 🔑 Credenciais de Teste
| Tipo | Usuário | Senha | Dashboard |
|------|---------|-------|-----------|
| **SuperAdmin** | `superadmin` | `Admin@2025!` | `/dashboard/admin` |
| **Admin Empresa** | `takeshi_yamamoto` | `Takeshi@2025` | `/dashboard/empresa` |
| **Recrutador** | `maria_santos` | `Maria@2025` | `/dashboard/empresa` |
| **Candidato** | `paulo_santos` | `User@2025` | `/dashboard/candidato` |
---
## 📖 Documentação da API
### Swagger UI
Acesse a documentação interativa em: **http://localhost:8080/swagger/index.html**
### Endpoints Principais
| Método | Endpoint | Descrição | Autenticação |
|--------|----------|-----------|--------------|
| `GET` | `/health` | Health check | ❌ |
| `POST` | `/api/v1/auth/login` | Login | ❌ |
| `POST` | `/api/v1/companies` | Criar empresa | ❌ |
| `GET` | `/api/v1/users` | Listar usuários | ✅ JWT |
| `POST` | `/api/v1/users` | Criar usuário | ✅ JWT |
| `GET` | `/jobs` | Listar vagas | ❌ |
| `POST` | `/jobs` | Criar vaga | ✅ JWT |
---
## 📂 Estrutura de Pastas
```
gohorsejobs/
├── backend/ # API Go
│ ├── cmd/api/ # Entrypoint da aplicação
│ ├── internal/ # Código interno
│ │ ├── api/ # Handlers e middlewares (Clean Arch)
│ │ ├── core/ # Domain e UseCases (DDD)
│ │ ├── database/ # Conexão com banco
│ │ ├── dto/ # Data Transfer Objects
│ │ ├── handlers/ # Controllers (legacy)
│ │ ├── middleware/ # Middlewares de segurança
│ │ ├── models/ # Entidades do banco
│ │ ├── router/ # Configuração de rotas
│ │ ├── services/ # Lógica de negócios
│ │ └── utils/ # Utilitários (JWT, sanitizer)
│ ├── migrations/ # Migrations SQL
│ └── docs/ # Swagger gerado
├── frontend/ # Next.js App
│ └── src/
│ ├── app/ # App Router (páginas)
│ ├── components/ # Componentes React
│ ├── contexts/ # React Contexts
│ ├── hooks/ # Custom Hooks
│ └── lib/ # Utilitários
├── seeder-api/ # Seeder Node.js
│ └── src/
│ ├── seeders/ # Scripts de seed por entidade
│ └── index.js # Entrypoint
├── run_dev.sh # Script de desenvolvimento
└── README.md # Este arquivo
```
---
## 🤝 Contribuindo
1. Fork o projeto
2. Crie sua branch (`git checkout -b feature/AmazingFeature`)
3. Commit suas mudanças (`git commit -m 'feat: add amazing feature'`)
4. Push para a branch (`git push origin feature/AmazingFeature`)
5. Abra um Pull Request
---
## 📄 Licença
Este projeto está sob a licença MIT. Veja o arquivo [LICENSE](LICENSE) para mais detalhes.
---
<p align="center">
<sub>Desenvolvido com ❤️ pela equipe GoHorse</sub>
</p>

47
backend/.dockerignore Normal file
View file

@ -0,0 +1,47 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Build outputs
/main
*.exe
*.dll
*.so
*.dylib
# Test files
*_test.go
*.test
coverage.out
coverage.html
# IDE and editor
.idea/
.vscode/
*.swp
*.swo
*~
# Git
.git/
.gitignore
# Documentation (keep docs/ for swagger)
*.md
LICENSE
# Environment files (security)
.env
.env.*
!.env.example
# Temporary files
tmp/
temp/
*.tmp
*.log
# OS files
.DS_Store
Thumbs.db

View file

@ -1,37 +1,67 @@
# Build stage
# =============================================================================
# GoHorse Jobs Backend - Optimized Production Dockerfile
# =============================================================================
# -----------------------------------------------------------------------------
# Stage 1: Build
# -----------------------------------------------------------------------------
FROM golang:1.24-alpine AS builder
WORKDIR /app
WORKDIR /build
# Install build dependencies if needed (e.g. gcc for cgo, though we disable cgo)
RUN apk add --no-cache git
# Install minimal build dependencies
RUN apk add --no-cache git ca-certificates tzdata
# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download
RUN go mod download && go mod verify
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -o main ./cmd/api
# Build with optimizations:
# - CGO_ENABLED=0: Static binary (no C dependencies)
# - ldflags -s -w: Strip debug info for smaller binary
# - trimpath: Remove local paths from binary
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w -X main.Version=$(git describe --tags --always --dirty 2>/dev/null || echo 'dev')" \
-trimpath \
-o /app/main ./cmd/api
# Run stage
FROM alpine:latest
# -----------------------------------------------------------------------------
# Stage 2: Production (Minimal Image)
# -----------------------------------------------------------------------------
FROM alpine:3.19
# Security: Run as non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
# Install runtime dependencies
RUN apk add --no-cache ca-certificates
# Copy timezone data and CA certificates from builder
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copy binary from builder
# Copy binary and migrations
COPY --from=builder /app/main .
COPY --from=builder /build/migrations ./migrations
# Copy migrations (CRITICAL for auto-migration logic)
COPY --from=builder /app/migrations ./migrations
# Set ownership to non-root user
RUN chown -R appuser:appgroup /app
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 8080
# Environment variables should be passed at runtime, but we can set defaults
ENV PORT=8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:8080/health || exit 1
# Environment defaults
ENV PORT=8080 \
TZ=America/Sao_Paulo
CMD ["./main"]

View file

@ -1,196 +1,128 @@
# GoHorseJobs Backend
# Backend - GoHorse Jobs API
This is the backend for the GoHorseJobs application, built with Go and PostgreSQL.
[![Go](https://img.shields.io/badge/Go-1.24-00ADD8?style=flat-square&logo=go)](https://golang.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-336791?style=flat-square&logo=postgresql)](https://postgresql.org/)
## Architecture
### 1. **Core Module (Clean Architecture)**
The project now includes a strictly decoupled `internal/core` module that follows Clean Architecture and DDD principles.
- **Pure Domain**: `internal/core/domain/entity` (No external deps).
- **Ports**: `internal/core/ports` (Interfaces for Repositories/Services).
- **UseCases**: `internal/core/usecases` (Business Logic).
### 2. **Multi-Tenancy**
- **Strict Isolation**: All core tables (`core_companies`, `core_users`) use UUIDs and strict Tenant FKs.
- **Middleware**: `TenantGuard` automatically extracts Tenant context from JWTs.
### 3. **Swagger API Docs**
- **URL**: [http://localhost:8080/swagger/index.html](http://localhost:8080/swagger/index.html)
- **Generate**: `swag init -g cmd/api/main.go --parseDependency --parseInternal`
### 4. **Super Admin Access**
A seed migration is provided to create the initial system access.
- **Migration**: `migrations/010_seed_super_admin.sql`
- **User**: `admin@gohorse.com`
- **Password**: `password123`
## Prerequisites
- Docker
- Docker Compose
- Go 1.22+
## Getting Started
1. **Start the services:**
```bash
docker-compose up --build
```
This will start the PostgreSQL database and the Go API server.
Alternatively, use the running script in the root:
```bash
./run_dev.sh
```
2. **Access the API:**
The API will be available at `http://localhost:8080`.
- **Health Check:** `GET /health`
- **List Jobs:** `GET /jobs`
- **Create Job:** `POST /jobs`
## API Endpoints
### `GET /health`
Returns `200 OK` if the server is running.
### `GET /jobs`
Returns a list of all jobs.
**Response:**
```json
[
{
"id": 1,
"title": "Software Engineer",
"description": "Develop Go applications.",
"company_name": "Tech Corp",
"location": "Remote",
"salary_range": "$100k - $120k",
"created_at": "2023-10-27T10:00:00Z"
}
]
```
### `POST /jobs`
Creates a new job.
**Request Body:**
```json
{
"title": "Software Engineer",
"description": "Develop Go applications.",
"company_name": "Tech Corp",
"location": "Remote",
"salary_range": "$100k - $120k"
}
```
**Response:**
Returns the created job with its ID and creation timestamp.
API REST desenvolvida em Go seguindo princípios de **Clean Architecture** e **DDD**.
---
# 📚 Feature Documentation (Archives)
## 🏗️ Arquitetura
> [!NOTE]
> The following documentation describes features implemented in the application, including Frontend components and Local Database logic. They have been consolidated here for reference.
```
internal/
├── api/ # Clean Architecture Layer
│ ├── handlers/ # HTTP Handlers (Controllers)
│ └── middleware/ # Auth, CORS, Rate Limiting
├── core/ # Domain Layer (DDD)
│ ├── domain/entity/ # Entidades puras (sem deps externas)
│ ├── ports/ # Interfaces (Repository, Service)
│ └── usecases/ # Casos de uso (Business Logic)
├── infrastructure/ # Infrastructure Layer
│ ├── auth/ # JWT Service implementation
│ └── persistence/ # Repository implementations
├── handlers/ # Legacy controllers
├── middleware/ # Security middlewares
├── models/ # GORM models
├── services/ # Business logic (legacy)
└── utils/ # Helpers (JWT, Sanitizer)
```
## 🗄️ Local Database - Profile System (Legacy/Frontend)
---
Full local database system using localStorage to manage profile photos and user data.
## 🔒 Segurança
### 🚀 Usage
### Middlewares Implementados
#### 1. Page with Database
Access: `http://localhost:3000/profile-db`
| Middleware | Arquivo | Descrição |
|------------|---------|-----------|
| **Auth** | `middleware/auth.go` | Validação JWT + RBAC |
| **CORS** | `middleware/cors.go` | Whitelist de origens via `CORS_ORIGINS` |
| **Rate Limiting** | `middleware/rate_limit.go` | 100 req/min por IP |
| **Security Headers** | `middleware/security_headers.go` | OWASP headers (XSS, CSP, etc.) |
#### 2. Features Implemented
### Validação de JWT
**📸 Profile Picture Upload**
- ✅ Click to select image
- ✅ Automatic validation (JPG, PNG, GIF, WebP)
- ✅ 2MB limit per file
- ✅ Instant preview
- ✅ Auto-save to localStorage
- ✅ Loading indicator
- ✅ Remove photo button
O servidor valida no boot que `JWT_SECRET` tenha pelo menos 32 caracteres. Em produção (`ENV=production`), o servidor **não inicia** com secrets fracos.
**🗃️ Local Database**
- ✅ Auto-save to localStorage
- ✅ Persistence between sessions
- ✅ Export data (JSON backup)
- ✅ Clear all data
- ✅ User structure
- ✅ Creation/update timestamps
---
**🔧 useProfile Hook**
- ✅ Reactive state management
- ✅ Loading states
- ✅ Full CRUD
- ✅ Auto synchronization
## 📡 Endpoints
## 🏢 Company Dashboard (Frontend Features)
### Públicos
### ✅ Complete Features
| Método | Endpoint | Descrição |
|--------|----------|-----------|
| `GET` | `/health` | Health check |
| `GET` | `/swagger/*` | Documentação Swagger |
| `POST` | `/api/v1/auth/login` | Autenticação |
| `POST` | `/api/v1/companies` | Registro de empresa |
| `GET` | `/jobs` | Listar vagas |
#### 1⃣ Job Management
**Page:** `/dashboard/empresa/vagas`
- ✅ Full listing of published jobs
- ✅ Statistics per job
- ✅ Search and filters
- ✅ Quick actions: View, Edit, Pause, Delete
### Protegidos (JWT Required)
#### 2⃣ Application Management
**Page:** `/dashboard/empresa/candidaturas`
- ✅ View all received applications
- ✅ Statistics cards by status
- ✅ Tabs system
- ✅ Search by candidate name
- ✅ Quick actions: Approve, Reject, Email
| Método | Endpoint | Roles | Descrição |
|--------|----------|-------|-----------|
| `GET` | `/api/v1/users` | `superadmin`, `companyAdmin` | Listar usuários |
| `POST` | `/api/v1/users` | `superadmin`, `companyAdmin` | Criar usuário |
| `DELETE` | `/api/v1/users/{id}` | `superadmin` | Deletar usuário |
| `POST` | `/jobs` | `companyAdmin`, `recruiter` | Criar vaga |
#### 3⃣ Messaging System
**Page:** `/dashboard/empresa/mensagens`
- ✅ WhatsApp/Slack style chat interface
- ✅ Conversation list with unread counters
- ✅ Real-time message attachment
- ✅ Responsive design
---
#### 4⃣ Analytics & Reports
**Page:** `/dashboard/empresa/relatorios`
- ✅ Key metrics cards
- ✅ Period selector
- ✅ Conversion funnel
- ✅ Hiring time by role
## 🚀 Desenvolvimento
#### 5⃣ Company Profile
**Page:** `/dashboard/empresa/perfil`
- ✅ Real logo upload
- ✅ Basic info management
- ✅ Social media links
- ✅ Culture description
### Executar
### 🎨 Design System
```bash
# Copie o .env
cp .env.example .env
**Stack:**
- shadcn/ui
- Tailwind CSS
- Lucide Icons
- Framer Motion
# Execute
go run ./cmd/api
```
**Colors:**
- Primary: Blue
- Success: Green
- Warning: Yellow
- Danger: Red
- Muted: Gray
### Testes
```bash
# Todos os testes
go test ./...
# Com verbose
go test -v ./internal/middleware/... ./internal/utils/...
```
### Regenerar Swagger
```bash
swag init -g cmd/api/main.go --parseDependency --parseInternal
```
---
## 🐳 Docker
```bash
# Build
docker build -t gohorse-backend .
# Run
docker run -p 8080:8080 --env-file .env gohorse-backend
```
**Imagem otimizada:** ~73MB (Alpine + non-root user)
---
## 📁 Arquivos Importantes
| Arquivo | Descrição |
|---------|-----------|
| `cmd/api/main.go` | Entrypoint da aplicação |
| `internal/router/router.go` | Configuração de todas as rotas |
| `internal/database/database.go` | Conexão GORM com PostgreSQL |
| `migrations/*.sql` | Migrations do banco de dados |
| `docs/swagger.json` | Documentação OpenAPI gerada |

View file

@ -21,6 +21,15 @@ func main() {
log.Println("No .env file found or error loading it")
}
// Validate JWT_SECRET strength (must be at least 32 characters / 256 bits)
jwtSecret := os.Getenv("JWT_SECRET")
if jwtSecret == "" || len(jwtSecret) < 32 {
log.Println("⚠️ WARNING: JWT_SECRET is empty or too short (< 32 chars). Use a strong secret in production!")
if os.Getenv("ENV") == "production" {
log.Fatal("FATAL: Cannot start in production without strong JWT_SECRET")
}
}
database.InitDB()
database.RunMigrations()

View file

@ -0,0 +1,86 @@
# Internal - Backend Core
Este diretório contém toda a lógica interna do backend, seguindo princípios de **Clean Architecture**.
---
## 📁 Estrutura de Módulos
| Diretório | Camada | Responsabilidade |
|-----------|--------|------------------|
| `api/` | Interface | Handlers e middlewares (Clean Arch) |
| `core/` | Domain | Entidades, ports e use cases (DDD) |
| `database/` | Infrastructure | Conexão GORM com PostgreSQL |
| `dto/` | Interface | Data Transfer Objects (request/response) |
| `handlers/` | Interface | Controllers HTTP (legacy) |
| `infrastructure/` | Infrastructure | Implementações de ports |
| `middleware/` | Interface | Middlewares de segurança |
| `models/` | Infrastructure | Modelos GORM |
| `router/` | Interface | Configuração de rotas |
| `services/` | Application | Lógica de negócios (legacy) |
| `utils/` | Shared | Utilitários (JWT, Sanitizer) |
---
## 🏗️ Fluxo de Requisição
```
HTTP Request
┌─────────────┐
│ Middleware │ (Auth, CORS, Rate Limit, Security Headers)
└─────────────┘
┌─────────────┐
│ Router │ (router/router.go)
└─────────────┘
┌─────────────┐
│ Handler │ (api/handlers/ ou handlers/)
└─────────────┘
┌─────────────┐
│ UseCase │ (core/usecases/)
└─────────────┘
┌─────────────┐
│ Repository │ (infrastructure/persistence/)
└─────────────┘
┌─────────────┐
│ Database │ (PostgreSQL via GORM)
└─────────────┘
```
---
## 📦 Módulos Detalhados
### `api/`
Implementação Clean Architecture dos handlers e middlewares.
- `handlers/` - Controllers HTTP novos
- `middleware/` - Auth com JWT Service
### `core/`
Camada de domínio puro seguindo DDD.
- `domain/entity/` - Entidades sem dependências externas
- `ports/` - Interfaces de repositórios e serviços
- `usecases/` - Casos de uso (Login, CreateUser, etc.)
### `middleware/`
Middlewares de segurança aplicados globalmente.
- `auth.go` - Validação JWT + RBAC
- `cors.go` - Whitelist de origens
- `rate_limit.go` - 100 req/min por IP
- `security_headers.go` - Headers OWASP
### `utils/`
Utilitários compartilhados.
- `jwt.go` - Geração e validação de tokens
- `sanitizer.go` - Sanitização de inputs (XSS prevention)

View file

@ -0,0 +1,93 @@
# Middleware - Security Layer
Middlewares de segurança aplicados a todas as requisições HTTP.
---
## 📦 Middlewares Disponíveis
### `auth.go` - Autenticação JWT
Valida tokens JWT e extrai claims do usuário.
```go
// Uso em rotas protegidas
mux.Handle("/protected", AuthMiddleware(handler))
// Com verificação de role
mux.Handle("/admin", AuthMiddleware(RequireRole("superadmin")(handler)))
```
**Claims extraídas:**
- `UserID` - ID do usuário
- `Role` - Papel (superadmin, companyAdmin, recruiter, jobSeeker)
- `CompanyID` - ID da empresa (se aplicável)
---
### `cors.go` - Cross-Origin Resource Sharing
Configura origens permitidas via variável de ambiente.
```env
CORS_ORIGINS=http://localhost:3000,https://gohorsejobs.com
```
**Headers configurados:**
- `Access-Control-Allow-Origin` (whitelist)
- `Access-Control-Allow-Methods`
- `Access-Control-Allow-Headers`
- `Access-Control-Allow-Credentials`
---
### `rate_limit.go` - Rate Limiting
Limita requisições por IP para prevenir abusos.
**Configuração padrão:**
- 100 requisições por minuto por IP
- Retorna `429 Too Many Requests` quando excedido
**Headers de resposta:**
- `Retry-After: 60` (quando limitado)
---
### `security_headers.go` - Security Headers (OWASP)
Adiciona headers de segurança recomendados pela OWASP.
| Header | Valor | Proteção |
|--------|-------|----------|
| `X-Frame-Options` | `DENY` | Clickjacking |
| `X-Content-Type-Options` | `nosniff` | MIME sniffing |
| `X-XSS-Protection` | `1; mode=block` | XSS |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Vazamento de referrer |
| `Content-Security-Policy` | (configurado) | Injeção de conteúdo |
| `Permissions-Policy` | (configurado) | APIs perigosas |
---
### `logging.go` - Request Logging
Loga todas as requisições com método, path e duração.
---
## 🔗 Ordem de Aplicação
Os middlewares são aplicados na seguinte ordem (de fora para dentro):
1. **Security Headers** - Headers de segurança
2. **Rate Limiting** - Limitação de taxa
3. **CORS** - Cross-origin
4. **Logging** - Log de requisições
5. **Auth** - Autenticação (quando aplicável)
```go
// Em router.go
handler = CORSMiddleware(handler)
handler = RateLimitMiddleware(100, time.Minute)(handler)
handler = SecurityHeadersMiddleware(handler)
```

View file

@ -1,16 +1,39 @@
package middleware
import "net/http"
import (
"net/http"
"os"
"strings"
)
// CORSMiddleware handles Cross-Origin Resource Sharing
// IMPORTANT: Configure CORS_ORIGINS env var in production
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set CORS headers
w.Header().Set("Access-Control-Allow-Origin", "*") // TODO: Restrict in production
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
origins := os.Getenv("CORS_ORIGINS")
if origins == "" {
origins = "http://localhost:3000"
}
origin := r.Header.Get("Origin")
allowOrigin := ""
// Check if origin is in allowed list
for _, o := range strings.Split(origins, ",") {
if strings.TrimSpace(o) == origin {
allowOrigin = origin
break
}
}
if allowOrigin != "" {
w.Header().Set("Access-Control-Allow-Origin", allowOrigin)
}
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-CSRF-Token")
// Handle preflight requests
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return

View file

@ -0,0 +1,82 @@
package middleware
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestRateLimiter_isAllowed(t *testing.T) {
limiter := NewRateLimiter(3, time.Minute)
// First 3 requests should be allowed
for i := 0; i < 3; i++ {
if !limiter.isAllowed("192.168.1.1") {
t.Errorf("Request %d should be allowed", i+1)
}
}
// 4th request should be denied
if limiter.isAllowed("192.168.1.1") {
t.Error("Request 4 should be denied")
}
// Different IP should still be allowed
if !limiter.isAllowed("192.168.1.2") {
t.Error("Different IP should be allowed")
}
}
func TestRateLimitMiddleware(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
middleware := RateLimitMiddleware(2, time.Minute)(handler)
// Create test requests
for i := 0; i < 3; i++ {
req := httptest.NewRequest("GET", "/test", nil)
req.RemoteAddr = "192.168.1.100:12345"
rr := httptest.NewRecorder()
middleware.ServeHTTP(rr, req)
if i < 2 {
if rr.Code != http.StatusOK {
t.Errorf("Request %d: expected status 200, got %d", i+1, rr.Code)
}
} else {
if rr.Code != http.StatusTooManyRequests {
t.Errorf("Request %d: expected status 429, got %d", i+1, rr.Code)
}
}
}
}
func TestSecurityHeadersMiddleware(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
middleware := SecurityHeadersMiddleware(handler)
req := httptest.NewRequest("GET", "/test", nil)
rr := httptest.NewRecorder()
middleware.ServeHTTP(rr, req)
expectedHeaders := map[string]string{
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
}
for header, expected := range expectedHeaders {
actual := rr.Header().Get(header)
if actual != expected {
t.Errorf("Header %s: expected %q, got %q", header, expected, actual)
}
}
}

View file

@ -0,0 +1,117 @@
package middleware
import (
"net/http"
"sync"
"time"
)
// RateLimiter implements a simple in-memory rate limiter
type RateLimiter struct {
visitors map[string]*visitor
mu sync.RWMutex
rate int // requests allowed
window time.Duration // time window
}
type visitor struct {
count int
lastReset time.Time
}
// NewRateLimiter creates a rate limiter with specified requests per window
func NewRateLimiter(rate int, window time.Duration) *RateLimiter {
rl := &RateLimiter{
visitors: make(map[string]*visitor),
rate: rate,
window: window,
}
// Cleanup old entries periodically
go rl.cleanup()
return rl
}
func (rl *RateLimiter) cleanup() {
for {
time.Sleep(rl.window)
rl.mu.Lock()
for ip, v := range rl.visitors {
if time.Since(v.lastReset) > rl.window*2 {
delete(rl.visitors, ip)
}
}
rl.mu.Unlock()
}
}
func (rl *RateLimiter) isAllowed(ip string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
v, exists := rl.visitors[ip]
now := time.Now()
if !exists {
rl.visitors[ip] = &visitor{count: 1, lastReset: now}
return true
}
// Reset window if needed
if now.Sub(v.lastReset) > rl.window {
v.count = 1
v.lastReset = now
return true
}
if v.count >= rl.rate {
return false
}
v.count++
return true
}
// getIP extracts client IP from request
func getIP(r *http.Request) string {
// Check X-Forwarded-For first (for proxied requests)
xff := r.Header.Get("X-Forwarded-For")
if xff != "" {
// Take the first IP in the chain
for i := 0; i < len(xff); i++ {
if xff[i] == ',' {
return xff[:i]
}
}
return xff
}
// Check X-Real-IP
xri := r.Header.Get("X-Real-IP")
if xri != "" {
return xri
}
// Fallback to RemoteAddr
return r.RemoteAddr
}
// RateLimitMiddleware returns a middleware that limits requests per IP
func RateLimitMiddleware(rate int, window time.Duration) func(http.Handler) http.Handler {
limiter := NewRateLimiter(rate, window)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := getIP(r)
if !limiter.isAllowed(ip) {
w.Header().Set("Retry-After", "60")
http.Error(w, "Rate limit exceeded. Please try again later.", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}

View file

@ -0,0 +1,32 @@
package middleware
import "net/http"
// SecurityHeadersMiddleware adds essential security headers to all responses
// Based on OWASP guidelines
func SecurityHeadersMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Prevent clickjacking
w.Header().Set("X-Frame-Options", "DENY")
// Prevent MIME sniffing
w.Header().Set("X-Content-Type-Options", "nosniff")
// Enable XSS filter
w.Header().Set("X-XSS-Protection", "1; mode=block")
// Only send referrer for same-origin
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
// Permissions policy (disable potentially dangerous features)
w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
// Content Security Policy - adjust as needed for your frontend
w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'")
// HSTS - uncomment in production with HTTPS
// w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
next.ServeHTTP(w, r)
})
}

View file

@ -3,6 +3,7 @@ package router
import (
"net/http"
"os"
"time"
"github.com/rede5/gohorsejobs/backend/internal/api/middleware"
"github.com/rede5/gohorsejobs/backend/internal/database"
@ -16,9 +17,10 @@ import (
tenantUC "github.com/rede5/gohorsejobs/backend/internal/core/usecases/tenant"
userUC "github.com/rede5/gohorsejobs/backend/internal/core/usecases/user"
authInfra "github.com/rede5/gohorsejobs/backend/internal/infrastructure/auth"
legacyMiddleware "github.com/rede5/gohorsejobs/backend/internal/middleware"
httpSwagger "github.com/swaggo/http-swagger/v2"
_ "github.com/rede5/gohorsejobs/backend/docs" // Import generated docs
httpSwagger "github.com/swaggo/http-swagger/v2"
)
func NewRouter() http.Handler {
@ -90,8 +92,12 @@ func NewRouter() http.Handler {
// Swagger Route
mux.HandleFunc("/swagger/", httpSwagger.WrapHandler)
// Wrap the mux with CORS middleware
handler := middleware.CORSMiddleware(mux)
// Apply middleware chain: Security Headers -> Rate Limiting -> CORS -> Router
// Order matters: outer middleware runs first
var handler http.Handler = mux
handler = middleware.CORSMiddleware(handler)
handler = legacyMiddleware.RateLimitMiddleware(100, time.Minute)(handler) // 100 req/min per IP
handler = legacyMiddleware.SecurityHeadersMiddleware(handler)
return handler
}

View file

@ -0,0 +1,89 @@
package utils
import (
"html"
"regexp"
"strings"
"unicode/utf8"
)
// Sanitizer provides input sanitization utilities
type Sanitizer struct {
// Max lengths for common fields
MaxNameLength int
MaxDescriptionLength int
MaxEmailLength int
}
// DefaultSanitizer returns a sanitizer with default settings
func DefaultSanitizer() *Sanitizer {
return &Sanitizer{
MaxNameLength: 255,
MaxDescriptionLength: 10000,
MaxEmailLength: 320,
}
}
// SanitizeString escapes HTML and trims whitespace
func (s *Sanitizer) SanitizeString(input string) string {
if input == "" {
return ""
}
// Trim whitespace
result := strings.TrimSpace(input)
// Escape HTML entities to prevent XSS
result = html.EscapeString(result)
return result
}
// SanitizeName sanitizes a name field
func (s *Sanitizer) SanitizeName(input string) string {
sanitized := s.SanitizeString(input)
if utf8.RuneCountInString(sanitized) > s.MaxNameLength {
runes := []rune(sanitized)
sanitized = string(runes[:s.MaxNameLength])
}
return sanitized
}
// SanitizeEmail sanitizes and validates email format
func (s *Sanitizer) SanitizeEmail(input string) string {
sanitized := strings.TrimSpace(strings.ToLower(input))
if utf8.RuneCountInString(sanitized) > s.MaxEmailLength {
return ""
}
return sanitized
}
// SanitizeDescription sanitizes long text fields
func (s *Sanitizer) SanitizeDescription(input string) string {
sanitized := s.SanitizeString(input)
if utf8.RuneCountInString(sanitized) > s.MaxDescriptionLength {
runes := []rune(sanitized)
sanitized = string(runes[:s.MaxDescriptionLength])
}
return sanitized
}
// SanitizeSlug creates a URL-safe slug
func (s *Sanitizer) SanitizeSlug(input string) string {
// Convert to lowercase
result := strings.ToLower(strings.TrimSpace(input))
// Replace spaces with hyphens
result = strings.ReplaceAll(result, " ", "-")
// Remove non-alphanumeric characters except hyphens
reg := regexp.MustCompile(`[^a-z0-9-]`)
result = reg.ReplaceAllString(result, "")
// Remove multiple consecutive hyphens
reg = regexp.MustCompile(`-+`)
result = reg.ReplaceAllString(result, "-")
// Trim hyphens from ends
result = strings.Trim(result, "-")
return result
}
// StripHTML removes all HTML tags from input
func StripHTML(input string) string {
reg := regexp.MustCompile(`<[^>]*>`)
return reg.ReplaceAllString(input, "")
}

View file

@ -0,0 +1,101 @@
package utils
import (
"testing"
)
func TestSanitizeString(t *testing.T) {
s := DefaultSanitizer()
tests := []struct {
name string
input string
expected string
}{
{"simple text", "hello world", "hello world"},
{"with whitespace", " hello ", "hello"},
{"with html", "<script>alert('xss')</script>", "&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;"},
{"empty string", "", ""},
{"special chars", "café & thé", "café &amp; thé"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := s.SanitizeString(tt.input)
if result != tt.expected {
t.Errorf("SanitizeString(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestSanitizeSlug(t *testing.T) {
s := DefaultSanitizer()
tests := []struct {
name string
input string
expected string
}{
{"simple text", "Hello World", "hello-world"},
{"special chars", "Café & Thé!", "caf-th"},
{"multiple spaces", "hello world", "hello-world"},
{"already slug", "hello-world", "hello-world"},
{"numbers", "test 123", "test-123"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := s.SanitizeSlug(tt.input)
if result != tt.expected {
t.Errorf("SanitizeSlug(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestSanitizeName(t *testing.T) {
s := DefaultSanitizer()
s.MaxNameLength = 10
tests := []struct {
name string
input string
expected string
}{
{"short name", "John", "John"},
{"max length", "1234567890", "1234567890"},
{"over limit", "12345678901", "1234567890"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := s.SanitizeName(tt.input)
if result != tt.expected {
t.Errorf("SanitizeName(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestStripHTML(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"simple html", "<p>hello</p>", "hello"},
{"script tag", "<script>alert('xss')</script>", "alert('xss')"},
{"nested tags", "<div><span>text</span></div>", "text"},
{"no html", "plain text", "plain text"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := StripHTML(tt.input)
if result != tt.expected {
t.Errorf("StripHTML(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}

56
frontend/.dockerignore Normal file
View file

@ -0,0 +1,56 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Build outputs
.next/
out/
build/
dist/
# Testing
coverage/
.nyc_output/
*.lcov
# IDE and editor
.idea/
.vscode/
*.swp
*.swo
*~
# Git
.git/
.gitignore
# Documentation
*.md
LICENSE
# Environment files (security)
.env
.env.*
!.env.example
# Debug and logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
*.log
# TypeScript cache
*.tsbuildinfo
# OS files
.DS_Store
Thumbs.db
# Storybook
storybook-static/
# Misc
*.pem
.vercel

View file

@ -0,0 +1,27 @@
{
"extends": ["stylelint-config-standard"],
"rules": {
"at-rule-no-unknown": [
true,
{
"ignoreAtRules": [
"tailwind",
"apply",
"layer",
"config",
"theme",
"custom-variant",
"import"
]
}
],
"function-no-unknown": [
true,
{
"ignoreFunctions": ["theme", "oklch"]
}
],
"import-notation": null,
"no-descending-specificity": null
}
}

68
frontend/Dockerfile Normal file
View file

@ -0,0 +1,68 @@
# =============================================================================
# GoHorse Jobs Frontend - Optimized Production Dockerfile
# =============================================================================
# -----------------------------------------------------------------------------
# Stage 1: Dependencies
# -----------------------------------------------------------------------------
FROM node:20-alpine AS deps
WORKDIR /app
# Install dependencies only when needed
COPY package.json package-lock.json* ./
RUN npm ci --only=production --ignore-scripts && \
npm cache clean --force
# -----------------------------------------------------------------------------
# Stage 2: Builder
# -----------------------------------------------------------------------------
FROM node:20-alpine AS builder
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build arguments for environment
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
# Build the application
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# -----------------------------------------------------------------------------
# Stage 3: Production Runner
# -----------------------------------------------------------------------------
FROM node:20-alpine AS runner
WORKDIR /app
# Security: Run as non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -u 1001 -S nextjs -G nodejs
# Set production environment
ENV NODE_ENV=production \
NEXT_TELEMETRY_DISABLED=1 \
PORT=3000
# Copy only necessary files for production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
# Set ownership to non-root user
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000 || exit 1
CMD ["node", "server.js"]

View file

@ -1,48 +1,145 @@
# GoHorseJobs Frontend
# Frontend - GoHorse Jobs
This is the frontend for the GoHorseJobs application, built with **Next.js 15**, **Tailwind CSS**, and **shadcn/ui**.
[![Next.js](https://img.shields.io/badge/Next.js-15-black?style=flat-square&logo=next.js)](https://nextjs.org/)
[![Tailwind CSS](https://img.shields.io/badge/Tailwind-4-06B6D4?style=flat-square&logo=tailwindcss)](https://tailwindcss.com/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?style=flat-square&logo=typescript)](https://typescriptlang.org/)
## 🚀 Getting Started
Frontend da plataforma GoHorse Jobs construído com **Next.js 15** e **App Router**.
### Prerequisites
- Node.js 18+
- npm / yarn / pnpm
---
### Installation
## 🏗️ Arquitetura
```
src/
├── app/ # App Router (Páginas)
│ ├── dashboard/ # Área logada
│ │ ├── admin/ # Painel SuperAdmin
│ │ ├── empresa/ # Painel Empresa
│ │ └── candidato/ # Painel Candidato
│ ├── login/ # Autenticação
│ ├── vagas/ # Listagem pública
│ └── layout.tsx # Layout raiz
├── components/ # Componentes React
│ ├── ui/ # shadcn/ui primitives
│ ├── forms/ # Formulários reutilizáveis
│ └── (feature)/ # Componentes por feature
├── contexts/ # React Contexts
│ └── AuthContext.tsx # Autenticação global
├── hooks/ # Custom Hooks
│ ├── useAuth.ts # Hook de autenticação
│ └── useProfile.ts # Hook de perfil
└── lib/ # Utilitários
├── api.ts # Cliente HTTP
└── utils.ts # Helpers gerais
```
---
## 🎨 Design System
### Tecnologias
| Tecnologia | Uso |
|------------|-----|
| **shadcn/ui** | Componentes base (Radix UI) |
| **Tailwind CSS 4** | Estilização utility-first |
| **Lucide Icons** | Ícones |
| **Framer Motion** | Animações |
### Tema
O tema está definido em `src/app/globals.css` usando CSS variables com cores `oklch()`:
- **Primary**: Laranja (`oklch(0.68 0.22 45)`)
- **Background**: Light/Dark mode automático
- **Componentes**: Herdam do design system
---
## 📱 Páginas Principais
| Rota | Descrição | Acesso |
|------|-----------|--------|
| `/` | Landing page | Público |
| `/vagas` | Listagem de vagas | Público |
| `/login` | Autenticação | Público |
| `/dashboard/admin` | Painel admin | SuperAdmin |
| `/dashboard/empresa` | Painel empresa | CompanyAdmin, Recruiter |
| `/dashboard/candidato` | Painel candidato | JobSeeker |
---
## 🚀 Desenvolvimento
### Instalar dependências
```bash
cd frontend
npm install
```
### Running Development Server
### Executar em desenvolvimento
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser.
### Build de produção
## 🛠️ Technology Stack
```bash
npm run build
npm run start
```
- **Framework:** [Next.js 15](https://nextjs.org/) (App Router)
- **Styling:** [Tailwind CSS](https://tailwindcss.com/)
- **Components:** [shadcn/ui](https://ui.shadcn.com/) (Radix UI)
- **Icons:** [Lucide React](https://lucide.dev/)
- **Forms:** React Hook Form + Zod
- **State Management:** React Context / Hooks
### Linting
## 📂 Project Structure
```bash
npm run lint
```
- `/src/app` - App Router pages and layouts
- `/src/components` - Reusable UI components
- `/src/components/ui` - shadcn/ui primitives
- `/src/hooks` - Custom React hooks
- `/src/lib` - Utility functions and libraries
---
## ✨ Key Features
## 🐳 Docker
- **Company Dashboard:** Manage jobs, applications, and messages.
- **Candidate Portal:** View and apply for jobs.
- **Profile Management:** Local database integration for profile pictures.
- **Responsive Design:** Mobile-first approach.
```bash
# Build
docker build -t gohorse-frontend .
# Run
docker run -p 3000:3000 gohorse-frontend
```
**Nota**: Requer `output: "standalone"` no `next.config.ts` (já configurado).
---
## 📁 Componentes Principais
| Componente | Descrição |
|------------|-----------|
| `components/ui/*` | Primitivos shadcn/ui |
| `components/dashboard-*` | Componentes do dashboard |
| `components/job-*` | Componentes de vagas |
| `components/sidebar.tsx` | Navegação lateral |
---
## 🔧 Configuração
### Variáveis de Ambiente
```env
NEXT_PUBLIC_API_URL=http://localhost:8080
```
### TypeScript
Configurado com strict mode em `tsconfig.json`.
### ESLint
Configurado com `next/core-web-vitals` e `next/typescript`.

View file

@ -1,7 +1,22 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
// Use standalone output for Docker (smaller image)
output: "standalone",
// Performance optimizations
poweredByHeader: false, // Remove X-Powered-By header (security)
compress: true, // Enable gzip compression
// Optional: Configure allowed image domains
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**",
},
],
},
};
export default nextConfig;

43
frontend/src/README.md Normal file
View file

@ -0,0 +1,43 @@
# Frontend Source
Código fonte do frontend Next.js.
---
## 📁 Estrutura
| Diretório | Descrição |
|-----------|-----------|
| `app/` | App Router - páginas e layouts |
| `components/` | Componentes React reutilizáveis |
| `contexts/` | React Contexts (Auth, Theme) |
| `hooks/` | Custom hooks |
| `lib/` | Utilitários e configurações |
---
## 🔑 Contextos
### `AuthContext.tsx`
Gerencia autenticação global:
- Estado do usuário logado
- Funções de login/logout
- Token JWT em localStorage
---
## 🪝 Hooks
| Hook | Descrição |
|------|-----------|
| `useAuth` | Acesso ao contexto de autenticação |
| `useProfile` | Gerenciamento de perfil de usuário |
---
## 📦 Lib
| Arquivo | Descrição |
|---------|-----------|
| `utils.ts` | Funções helper (cn, formatDate) |
| `api.ts` | Cliente HTTP para backend |

View file

@ -0,0 +1,53 @@
# Components
Componentes React reutilizáveis da aplicação.
---
## 📁 Estrutura
| Diretório | Descrição |
|-----------|-----------|
| `ui/` | Primitivos shadcn/ui (Button, Card, Dialog, etc.) |
| `forms/` | Componentes de formulário |
| `*.tsx` | Componentes de feature específicos |
---
## 🎨 Componentes UI (shadcn/ui)
Componentes base do design system, instalados via:
```bash
npx shadcn-ui@latest add [component]
```
| Componente | Uso |
|------------|-----|
| `Button` | Botões com variantes |
| `Card` | Containers de conteúdo |
| `Dialog` | Modais |
| `Input` | Campos de texto |
| `Select` | Dropdowns |
| `Table` | Tabelas de dados |
| `Tabs` | Navegação por abas |
| `Toast` | Notificações |
---
## 🧩 Componentes de Feature
| Componente | Descrição |
|------------|-----------|
| `sidebar.tsx` | Navegação lateral do dashboard |
| `dashboard-header.tsx` | Header do dashboard |
| `job-card.tsx` | Card de vaga de emprego |
| `application-card.tsx` | Card de candidatura |
---
## ✅ Convenções
1. **Nomenclatura**: kebab-case para arquivos (`job-card.tsx`)
2. **Exports**: Named exports para componentes
3. **Props**: Interface definida no mesmo arquivo
4. **Estilização**: Tailwind CSS + cn() utility

View file

@ -1,116 +1,98 @@
# Todai Jobs - Seeder API
# Seeder API
Microservice for seeding the Todai Jobs database with realistic sample data.
[![Node.js](https://img.shields.io/badge/Node.js-20-339933?style=flat-square&logo=node.js)](https://nodejs.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-16-336791?style=flat-square&logo=postgresql)](https://postgresql.org/)
## Overview
Microserviço para popular o banco de dados com dados realistas de teste.
This seeder populates the database with:
- ✅ 1 SuperAdmin user
- ✅ 10 real Japanese companies (Toyota, Honda, Denso, etc.)
- ✅ 8 Company Admins and Recruiters
- ✅ 30 Foreign Job Seekers (Brazilian, Filipino, Nepalese, etc.)
- ✅ 50 Realistic job listings
- ✅ 20 Sample applications
- ✅ 47 Japanese prefectures
- ✅ Cities for Aichi and Tokyo
---
## Installation
## 📊 Dados Gerados
| Entidade | Quantidade | Descrição |
|----------|------------|-----------|
| **SuperAdmin** | 1 | Usuário administrador do sistema |
| **Empresas** | 10 | Empresas reais (Toyota, Honda, etc.) |
| **Admins/Recruiters** | 8 | Usuários de empresas |
| **Candidatos** | 30 | Job seekers de várias nacionalidades |
| **Vagas** | 50 | Vagas realistas com requisitos |
| **Candidaturas** | 20 | Aplicações de exemplo |
| **Prefeituras** | 47 | Todas as prefeituras japonesas |
| **Cidades** | ~50 | Cidades de Aichi e Tokyo |
---
## 🚀 Uso
### Instalação
```bash
cd seeder-api
npm install
```
## Configuration
### Configuração
Create a `.env` file or use default values:
Crie um `.env` baseado no `.env.example`:
```env
DB_HOST=172.28.22.171
DB_HOST=localhost
DB_PORT=5432
DB_USER=usuario
DB_PASSWORD=senha123
DB_NAME=todaijobs
DB_USER=postgres
DB_PASSWORD=yourpassword
DB_NAME=gohorsejobs
```
## Usage
### Comandos
### Full Seed
```bash
npm run seed
```
| Comando | Descrição |
|---------|-----------|
| `npm run seed` | Popula todas as tabelas |
| `npm run seed:reset` | Limpa e repopula tudo |
| `npm run seed:users` | Apenas usuários |
| `npm run seed:companies` | Apenas empresas |
| `npm run seed:jobs` | Apenas vagas |
### Reset Database (Drop all tables)
```bash
npm run seed:reset
```
---
### Seed Specific Tables
```bash
npm run seed:users
npm run seed:companies
npm run seed:jobs
```
## Login Credentials
## 🔑 Credenciais Geradas
### SuperAdmin
- **Identifier**: `superadmin`
- **Password**: `Admin@2025!`
- **Login**: `superadmin`
- **Senha**: `Admin@2025!`
### Company Admin (Multi-company: Toyota & Denso)
- **Identifier**: `takeshi_yamamoto`
- **Password**: `Takeshi@2025`
### Admin de Empresa
- **Login**: `takeshi_yamamoto`
- **Senha**: `Takeshi@2025`
### Recruiter (Honda)
- **Identifier**: `maria_santos`
- **Password**: `Maria@2025`
### Recrutador
- **Login**: `maria_santos`
- **Senha**: `Maria@2025`
### Job Seeker
- **Identifier**: `paulo_santos`
- **Password**: `User@2025`
### Candidato (todos usam mesma senha)
- **Login**: `paulo_santos`
- **Senha**: `User@2025`
_All job seekers use password: `User@2025`_
---
## Seeding Order
## 📁 Estrutura
The seeder respects foreign key dependencies:
1. **Prefectures** (47 Japanese prefectures)
2. **Cities** (Aichi + Tokyo municipalities)
3. **Users** (SuperAdmin, Admins, Recruiters, JobSeekers)
4. **Companies** (10 major Japanese companies)
5. **UserCompany** (Multi-tenant associations)
6. **Jobs** (50 job listings)
7. **Applications** (20 sample applications)
## Database Schema
All tables are created via migrations in `/backend/migrations/`. This seeder only populates data.
## Development
The seeder uses:
- **pg**: PostgreSQL client
- **bcrypt**: Password hashing
- **dotenv**: Environment variables
## Notes
- Run migrations before seeding
- Use `--reset` flag carefully (destroys all data)
- Default passwords are for development only
## Examples
```bash
# Fresh start
npm run seed:reset
cd ../backend
psql -h 172.28.22.171 -U usuario -d todaijobs -f migrations/*.sql
cd ../seeder-api
npm run seed
# Update data
npm run seed
```
src/
├── index.js # Entrypoint principal
├── config.js # Configuração de BD
└── seeders/
├── users.js # Seed de usuários
├── companies.js # Seed de empresas
├── jobs.js # Seed de vagas
├── cities.js # Seed de cidades
└── applications.js # Seed de candidaturas
```
---
## ⚠️ Importante
- Execute as **migrations** antes do seed
- Use apenas em ambiente de **desenvolvimento**
- Senhas padrão são **apenas para testes**