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
324
README.md
324
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
|
```mermaid
|
||||||
graph TD
|
graph TB
|
||||||
User((User))
|
subgraph Frontend
|
||||||
Frontend[Frontend (Next.js)]
|
A[Next.js 15<br/>App Router]
|
||||||
Backend[Backend (Go/Gin)]
|
end
|
||||||
Seeder[Seeder API (Node.js)]
|
|
||||||
DB[(PostgreSQL)]
|
subgraph Backend
|
||||||
|
B[Go API<br/>Clean Architecture]
|
||||||
User --> Frontend
|
end
|
||||||
Frontend -->|HTTP/REST| Backend
|
|
||||||
Seeder -->|Writes| DB
|
subgraph Database
|
||||||
Backend -->|Reads/Writes| DB
|
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
|
| Componente | Padrão | Descrição |
|
||||||
We have provided a convenience script to start the backend quickly.
|
|------------|--------|-----------|
|
||||||
|
| **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
|
```bash
|
||||||
|
# Clone o repositório
|
||||||
|
git clone https://github.com/rede5/gohorsejobs.git
|
||||||
|
cd gohorsejobs
|
||||||
|
|
||||||
|
# Execute o script de desenvolvimento
|
||||||
./run_dev.sh
|
./run_dev.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Run the Frontend
|
### Opção 2: Manual (Passo a Passo)
|
||||||
|
|
||||||
```bash
|
```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
|
cd frontend
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
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 ✅
|
### Backend (`backend/.env`)
|
||||||
- [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)
|
|
||||||
|
|
||||||
### In Progress 🚧
|
| Variável | Descrição | Exemplo |
|
||||||
- [ ] Integration of complete Frontend-Backend flow
|
|----------|-----------|---------|
|
||||||
- [ ] Advanced Search Filters
|
| `DB_HOST` | Host do PostgreSQL | `localhost` |
|
||||||
- [ ] Real-time Notifications
|
| `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
|
### Frontend (`frontend/.env`)
|
||||||
- [Backend Documentation](./backend/README.md)
|
|
||||||
- [Frontend Documentation](./frontend/README.md)
|
| Variável | Descrição | Exemplo |
|
||||||
- [Seeder Documentation](./seeder-api/README.md)
|
|----------|-----------|---------|
|
||||||
|
| `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
|
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)
|
# Install minimal build dependencies
|
||||||
RUN apk add --no-cache git
|
RUN apk add --no-cache git ca-certificates tzdata
|
||||||
|
|
||||||
|
# Cache dependencies
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download && go mod verify
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application
|
# Build with optimizations:
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -o main ./cmd/api
|
# - 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
|
WORKDIR /app
|
||||||
|
|
||||||
# Install runtime dependencies
|
# Copy timezone data and CA certificates from builder
|
||||||
RUN apk add --no-cache ca-certificates
|
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 /app/main .
|
||||||
|
COPY --from=builder /build/migrations ./migrations
|
||||||
|
|
||||||
# Copy migrations (CRITICAL for auto-migration logic)
|
# Set ownership to non-root user
|
||||||
COPY --from=builder /app/migrations ./migrations
|
RUN chown -R appuser:appgroup /app
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER appuser
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Environment variables should be passed at runtime, but we can set defaults
|
# Health check
|
||||||
ENV PORT=8080
|
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"]
|
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
|
API REST desenvolvida em Go seguindo princípios de **Clean Architecture** e **DDD**.
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 📚 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
|
| Middleware | Arquivo | Descrição |
|
||||||
Access: `http://localhost:3000/profile-db`
|
|------------|---------|-----------|
|
||||||
|
| **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**
|
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.
|
||||||
- ✅ 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
|
|
||||||
|
|
||||||
**🗃️ Local Database**
|
---
|
||||||
- ✅ Auto-save to localStorage
|
|
||||||
- ✅ Persistence between sessions
|
|
||||||
- ✅ Export data (JSON backup)
|
|
||||||
- ✅ Clear all data
|
|
||||||
- ✅ User structure
|
|
||||||
- ✅ Creation/update timestamps
|
|
||||||
|
|
||||||
**🔧 useProfile Hook**
|
## 📡 Endpoints
|
||||||
- ✅ Reactive state management
|
|
||||||
- ✅ Loading states
|
|
||||||
- ✅ Full CRUD
|
|
||||||
- ✅ Auto synchronization
|
|
||||||
|
|
||||||
## 🏢 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
|
### Protegidos (JWT Required)
|
||||||
**Page:** `/dashboard/empresa/vagas`
|
|
||||||
- ✅ Full listing of published jobs
|
|
||||||
- ✅ Statistics per job
|
|
||||||
- ✅ Search and filters
|
|
||||||
- ✅ Quick actions: View, Edit, Pause, Delete
|
|
||||||
|
|
||||||
#### 2️⃣ Application Management
|
| Método | Endpoint | Roles | Descrição |
|
||||||
**Page:** `/dashboard/empresa/candidaturas`
|
|--------|----------|-------|-----------|
|
||||||
- ✅ View all received applications
|
| `GET` | `/api/v1/users` | `superadmin`, `companyAdmin` | Listar usuários |
|
||||||
- ✅ Statistics cards by status
|
| `POST` | `/api/v1/users` | `superadmin`, `companyAdmin` | Criar usuário |
|
||||||
- ✅ Tabs system
|
| `DELETE` | `/api/v1/users/{id}` | `superadmin` | Deletar usuário |
|
||||||
- ✅ Search by candidate name
|
| `POST` | `/jobs` | `companyAdmin`, `recruiter` | Criar vaga |
|
||||||
- ✅ Quick actions: Approve, Reject, Email
|
|
||||||
|
|
||||||
#### 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
|
## 🚀 Desenvolvimento
|
||||||
**Page:** `/dashboard/empresa/relatorios`
|
|
||||||
- ✅ Key metrics cards
|
|
||||||
- ✅ Period selector
|
|
||||||
- ✅ Conversion funnel
|
|
||||||
- ✅ Hiring time by role
|
|
||||||
|
|
||||||
#### 5️⃣ Company Profile
|
### Executar
|
||||||
**Page:** `/dashboard/empresa/perfil`
|
|
||||||
- ✅ Real logo upload
|
|
||||||
- ✅ Basic info management
|
|
||||||
- ✅ Social media links
|
|
||||||
- ✅ Culture description
|
|
||||||
|
|
||||||
### 🎨 Design System
|
```bash
|
||||||
|
# Copie o .env
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
**Stack:**
|
# Execute
|
||||||
- shadcn/ui
|
go run ./cmd/api
|
||||||
- Tailwind CSS
|
```
|
||||||
- Lucide Icons
|
|
||||||
- Framer Motion
|
|
||||||
|
|
||||||
**Colors:**
|
### Testes
|
||||||
- Primary: Blue
|
|
||||||
- Success: Green
|
```bash
|
||||||
- Warning: Yellow
|
# Todos os testes
|
||||||
- Danger: Red
|
go test ./...
|
||||||
- Muted: Gray
|
|
||||||
|
# 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")
|
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.InitDB()
|
||||||
database.RunMigrations()
|
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
|
package middleware
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// CORSMiddleware handles Cross-Origin Resource Sharing
|
// CORSMiddleware handles Cross-Origin Resource Sharing
|
||||||
|
// IMPORTANT: Configure CORS_ORIGINS env var in production
|
||||||
func CORSMiddleware(next http.Handler) http.Handler {
|
func CORSMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Set CORS headers
|
origins := os.Getenv("CORS_ORIGINS")
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*") // TODO: Restrict in production
|
if origins == "" {
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
origins = "http://localhost:3000"
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
}
|
||||||
|
|
||||||
|
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" {
|
if r.Method == "OPTIONS" {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
return
|
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 (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rede5/gohorsejobs/backend/internal/api/middleware"
|
"github.com/rede5/gohorsejobs/backend/internal/api/middleware"
|
||||||
"github.com/rede5/gohorsejobs/backend/internal/database"
|
"github.com/rede5/gohorsejobs/backend/internal/database"
|
||||||
|
|
@ -16,9 +17,10 @@ import (
|
||||||
tenantUC "github.com/rede5/gohorsejobs/backend/internal/core/usecases/tenant"
|
tenantUC "github.com/rede5/gohorsejobs/backend/internal/core/usecases/tenant"
|
||||||
userUC "github.com/rede5/gohorsejobs/backend/internal/core/usecases/user"
|
userUC "github.com/rede5/gohorsejobs/backend/internal/core/usecases/user"
|
||||||
authInfra "github.com/rede5/gohorsejobs/backend/internal/infrastructure/auth"
|
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
|
_ "github.com/rede5/gohorsejobs/backend/docs" // Import generated docs
|
||||||
|
httpSwagger "github.com/swaggo/http-swagger/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRouter() http.Handler {
|
func NewRouter() http.Handler {
|
||||||
|
|
@ -90,8 +92,12 @@ func NewRouter() http.Handler {
|
||||||
// Swagger Route
|
// Swagger Route
|
||||||
mux.HandleFunc("/swagger/", httpSwagger.WrapHandler)
|
mux.HandleFunc("/swagger/", httpSwagger.WrapHandler)
|
||||||
|
|
||||||
// Wrap the mux with CORS middleware
|
// Apply middleware chain: Security Headers -> Rate Limiting -> CORS -> Router
|
||||||
handler := middleware.CORSMiddleware(mux)
|
// 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
|
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
|
```bash
|
||||||
cd frontend
|
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running Development Server
|
### Executar em desenvolvimento
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
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)
|
### Linting
|
||||||
- **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
|
|
||||||
|
|
||||||
## 📂 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.
|
```bash
|
||||||
- **Candidate Portal:** View and apply for jobs.
|
# Build
|
||||||
- **Profile Management:** Local database integration for profile pictures.
|
docker build -t gohorse-frontend .
|
||||||
- **Responsive Design:** Mobile-first approach.
|
|
||||||
|
# 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";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
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;
|
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
|
```bash
|
||||||
cd seeder-api
|
cd seeder-api
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
### Configuração
|
||||||
|
|
||||||
Create a `.env` file or use default values:
|
Crie um `.env` baseado no `.env.example`:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
DB_HOST=172.28.22.171
|
DB_HOST=localhost
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
DB_USER=usuario
|
DB_USER=postgres
|
||||||
DB_PASSWORD=senha123
|
DB_PASSWORD=yourpassword
|
||||||
DB_NAME=todaijobs
|
DB_NAME=gohorsejobs
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
### Comandos
|
||||||
|
|
||||||
### Full Seed
|
| Comando | Descrição |
|
||||||
```bash
|
|---------|-----------|
|
||||||
npm run seed
|
| `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
|
## 🔑 Credenciais Geradas
|
||||||
```bash
|
|
||||||
npm run seed:users
|
|
||||||
npm run seed:companies
|
|
||||||
npm run seed:jobs
|
|
||||||
```
|
|
||||||
|
|
||||||
## Login Credentials
|
|
||||||
|
|
||||||
### SuperAdmin
|
### SuperAdmin
|
||||||
- **Identifier**: `superadmin`
|
- **Login**: `superadmin`
|
||||||
- **Password**: `Admin@2025!`
|
- **Senha**: `Admin@2025!`
|
||||||
|
|
||||||
### Company Admin (Multi-company: Toyota & Denso)
|
### Admin de Empresa
|
||||||
- **Identifier**: `takeshi_yamamoto`
|
- **Login**: `takeshi_yamamoto`
|
||||||
- **Password**: `Takeshi@2025`
|
- **Senha**: `Takeshi@2025`
|
||||||
|
|
||||||
### Recruiter (Honda)
|
### Recrutador
|
||||||
- **Identifier**: `maria_santos`
|
- **Login**: `maria_santos`
|
||||||
- **Password**: `Maria@2025`
|
- **Senha**: `Maria@2025`
|
||||||
|
|
||||||
### Job Seeker
|
### Candidato (todos usam mesma senha)
|
||||||
- **Identifier**: `paulo_santos`
|
- **Login**: `paulo_santos`
|
||||||
- **Password**: `User@2025`
|
- **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