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:
parent
1c7ef95c1a
commit
7934afcf0d
22 changed files with 1578 additions and 362 deletions
322
README.md
322
README.md
|
|
@ -1,78 +1,306 @@
|
|||
# 🐴 GoHorse Jobs (Vagas para Tecnologia)
|
||||
# 🐴 GoHorse Jobs
|
||||
|
||||
A comprehensive recruitment platform connecting job seekers with opportunities in the technology sector.
|
||||
[](https://golang.org/)
|
||||
[](https://nextjs.org/)
|
||||
[](https://postgresql.org/)
|
||||
[](https://docker.com/)
|
||||
[](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
47
backend/.dockerignore
Normal 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
|
||||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -1,196 +1,128 @@
|
|||
# GoHorseJobs Backend
|
||||
# Backend - GoHorse Jobs API
|
||||
|
||||
This is the backend for the GoHorseJobs application, built with Go and PostgreSQL.
|
||||
[](https://golang.org/)
|
||||
[](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 |
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
86
backend/internal/README.md
Normal file
86
backend/internal/README.md
Normal 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)
|
||||
93
backend/internal/middleware/README.md
Normal file
93
backend/internal/middleware/README.md
Normal 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)
|
||||
```
|
||||
|
|
@ -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
|
||||
|
|
|
|||
82
backend/internal/middleware/middleware_test.go
Normal file
82
backend/internal/middleware/middleware_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
117
backend/internal/middleware/rate_limit.go
Normal file
117
backend/internal/middleware/rate_limit.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
32
backend/internal/middleware/security_headers.go
Normal file
32
backend/internal/middleware/security_headers.go
Normal 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)
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
89
backend/internal/utils/sanitizer.go
Normal file
89
backend/internal/utils/sanitizer.go
Normal 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, "")
|
||||
}
|
||||
101
backend/internal/utils/sanitizer_test.go
Normal file
101
backend/internal/utils/sanitizer_test.go
Normal 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>", "<script>alert('xss')</script>"},
|
||||
{"empty string", "", ""},
|
||||
{"special chars", "café & thé", "café & 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
56
frontend/.dockerignore
Normal 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
|
||||
27
frontend/.stylelintrc.json
Normal file
27
frontend/.stylelintrc.json
Normal 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
68
frontend/Dockerfile
Normal 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"]
|
||||
|
|
@ -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**.
|
||||
[](https://nextjs.org/)
|
||||
[](https://tailwindcss.com/)
|
||||
[](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`.
|
||||
|
|
|
|||
|
|
@ -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
43
frontend/src/README.md
Normal 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 |
|
||||
53
frontend/src/components/README.md
Normal file
53
frontend/src/components/README.md
Normal 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
|
||||
|
|
@ -1,116 +1,98 @@
|
|||
# Todai Jobs - Seeder API
|
||||
# Seeder API
|
||||
|
||||
Microservice for seeding the Todai Jobs database with realistic sample data.
|
||||
[](https://nodejs.org/)
|
||||
[](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**
|
||||
|
|
|
|||
Loading…
Reference in a new issue