4.4 KiB
🗄️ Database Architecture - GoHorseJobs
GoHorseJobs uses a single PostgreSQL 16+ database shared across all services (Backend API, NestJS Backoffice, Node.js Seeder).
🏗️ Core Entity-Relationship Diagram (ERD)
The core data model centers around users acting in different capacities (candidate, recruiter, admin) interacting with companies and jobs.
erDiagram
USERS ||--o{ USER_COMPANIES : "belongs to"
COMPANIES ||--o{ USER_COMPANIES : "contains"
COMPANIES ||--o{ JOBS : "posts"
USERS ||--o{ JOBS : "creates (author)"
USERS ||--o{ APPLICATIONS : "submits"
JOBS ||--o{ APPLICATIONS : "receives"
USERS ||--o{ FAVORITE_JOBS : "saves"
JOBS ||--o{ FAVORITE_JOBS : "favorited"
USERS ||--o{ TICKETS : "opens"
USERS ||--o{ TICKET_MESSAGES : "sends"
TICKETS ||--o{ TICKET_MESSAGES : "has"
USERS {
uuid id PK
string role "superadmin, admin, recruiter, candidate"
string identifier "username or email"
string email UK
string password_hash
boolean is_active
timestamp created_at
}
COMPANIES {
uuid id PK
string name
string document "CNPJ"
string domain UK
string location
string industry
string logo_url
string status "pending, active, rejected"
}
USER_COMPANIES {
uuid id PK
uuid user_id FK
uuid company_id FK
string role "owner, admin, member"
}
JOBS {
uuid id PK
uuid company_id FK
uuid author_id FK
string title
text description
string type "full-time, remote, etc"
decimal max_salary
decimal min_salary
string status "draft, published, closed"
}
APPLICATIONS {
uuid id PK
uuid job_id FK
uuid candidate_id FK
string status "pending, reviewing, interviewed, rejected, accepted"
text cover_letter
}
FAVORITE_JOBS {
uuid id PK
uuid user_id FK
uuid job_id FK
timestamp created_at
}
TICKETS {
uuid id PK
uuid user_id FK "nullable for guests"
string subject
string category "Support, Bug, Billing..."
string status "open, in_progress, resolved, closed"
string email "fallback for guests"
string name "fallback for guests"
}
🔑 Key Strategies
1. Primary Keys (UUID v7)
We aggressively use UUID v7 for all major business entities (users, companies, jobs).
- Why? UUID v7 contains a timestamp component, making it naturally sortable by insertion time (like SERIAL/auto-increment) while avoiding the predictability and distributed generation bottlenecks of standard integers.
2. Multi-Role Profiles
The system handles permissions through the role enum in the users table:
superadmin: Global control over GoHorseJobs.admin: Can moderate companies/jobs inside the Backoffice.recruiter: A user attached to acompany_idviauser_companieswho can postjobs.candidate: A standard user seeking jobs and makingapplications.
A single email can only correspond to one role globally. If a user needs another role, they must change it or use a different credential.
3. Migrations
Migrations are written in pure SQL and stored in backend/migrations/.
They follow a strict sequential numbering index (e.g., 000_schema.sql, 001_roles.sql, etc). The backend runtime executes these on startup if the database is out of sync.
4. Background Seeding
Seeding (populating test data) is explicitly decoupled from migrations. It lives isolated in the Node.js seeder-api.
To reset your local database to a clean, populated state:
# Deletes everything, remigrates, and inserts base data (except 153k cities)
cd seeder-api && npm run seed:lite
⚠️ Security Notes
The password_hash & Pepper
Bcrypt hashes stored in the standard users table require a secret pepper (PASSWORD_PEPPER).
- The raw hash stored in the DB cannot be cracked immediately due to the pepper.
- The seeder relies on reading the
PASSWORD_PEPPERfrom.envto forge the initial super-admin and test accounts hashes. If you change the.envpepper, the entire DB of seeded passwords immediately invalidates. You must re-runnpm run seed.