refactor(roles): rename companyAdmin->admin and jobSeeker->candidate
This commit is contained in:
parent
1b4f1d1555
commit
c1078563df
23 changed files with 115 additions and 80 deletions
|
|
@ -75,10 +75,10 @@ O endpoint `/jobs` suporta filtros avançados via query params:
|
|||
|
||||
| Método | Endpoint | Roles | Descrição |
|
||||
|--------|----------|-------|-----------|
|
||||
| `GET` | `/api/v1/users` | `superadmin`, `companyAdmin` | Listar usuários |
|
||||
| `POST` | `/api/v1/users` | `superadmin`, `companyAdmin` | Criar usuário |
|
||||
| `GET` | `/api/v1/users` | `superadmin`, `admin` | Listar usuários |
|
||||
| `POST` | `/api/v1/users` | `superadmin`, `admin` | Criar usuário |
|
||||
| `DELETE` | `/api/v1/users/{id}` | `superadmin` | Deletar usuário |
|
||||
| `POST` | `/jobs` | `companyAdmin`, `recruiter` | Criar vaga |
|
||||
| `POST` | `/jobs` | `admin`, `recruiter` | Criar vaga |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,20 @@ package entity
|
|||
|
||||
import "time"
|
||||
|
||||
// Role type alias
|
||||
type RoleString string
|
||||
|
||||
const (
|
||||
// RoleSuperAdmin is the platform administrator
|
||||
RoleSuperAdmin = "superadmin"
|
||||
// RoleAdmin is the company administrator (formerly admin)
|
||||
RoleAdmin = "admin"
|
||||
// RoleRecruiter is a recruiter within a company
|
||||
RoleRecruiter = "recruiter"
|
||||
// RoleCandidate is a job seeker (formerly candidate)
|
||||
RoleCandidate = "candidate"
|
||||
)
|
||||
|
||||
// User represents a user within a specific Tenant (Company).
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ type UserInfo struct {
|
|||
type CompanyInfo struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role"` // Role in this company (companyAdmin or recruiter)
|
||||
Role string `json:"role"` // Role in this company (admin or recruiter)
|
||||
}
|
||||
|
||||
// RegisterRequest represents user registration
|
||||
|
|
@ -44,7 +44,7 @@ type RegisterRequest struct {
|
|||
LineID *string `json:"lineId,omitempty"`
|
||||
Instagram *string `json:"instagram,omitempty"`
|
||||
Language string `json:"language" validate:"required,oneof=pt en es ja"`
|
||||
Role string `json:"role" validate:"required,oneof=jobSeeker recruiter companyAdmin"`
|
||||
Role string `json:"role" validate:"required,oneof=candidate recruiter admin"`
|
||||
}
|
||||
|
||||
// User represents a generic user profile
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ type UpdateCompanyRequest struct {
|
|||
type AssignUserToCompanyRequest struct {
|
||||
UserID string `json:"userId" validate:"required"`
|
||||
CompanyID string `json:"companyId" validate:"required"`
|
||||
Role string `json:"role" validate:"required,oneof=companyAdmin recruiter"`
|
||||
Role string `json:"role" validate:"required,oneof=admin recruiter"`
|
||||
Permissions map[string]interface{} `json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ func (r *UserRepository) Save(ctx context.Context, user *entity.User) (*entity.U
|
|||
`
|
||||
|
||||
var id string
|
||||
// Map the first role to the role column, default to 'jobSeeker'
|
||||
role := "jobSeeker"
|
||||
// Map the first role to the role column, default to 'candidate'
|
||||
role := "candidate"
|
||||
if len(user.Roles) > 0 {
|
||||
role = user.Roles[0].Name
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ mux.Handle("/admin", AuthMiddleware(RequireRole("superadmin")(handler)))
|
|||
|
||||
**Claims extraídas:**
|
||||
- `UserID` - ID do usuário
|
||||
- `Role` - Papel (superadmin, companyAdmin, recruiter, jobSeeker)
|
||||
- `Role` - Papel (superadmin, admin, recruiter, candidate)
|
||||
- `CompanyID` - ID da empresa (se aplicável)
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ type User struct {
|
|||
ID string `json:"id" db:"id"`
|
||||
Identifier string `json:"identifier" db:"identifier"`
|
||||
PasswordHash string `json:"-" db:"password_hash"` // Never expose password hash in JSON
|
||||
Role string `json:"role" db:"role"` // superadmin, companyAdmin, recruiter, jobSeeker
|
||||
Role string `json:"role" db:"role"` // superadmin, admin, recruiter, candidate
|
||||
|
||||
// Personal Info
|
||||
FullName string `json:"fullName" db:"full_name"`
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ type UserCompany struct {
|
|||
ID string `json:"id" db:"id"`
|
||||
UserID string `json:"userId" db:"user_id"`
|
||||
CompanyID string `json:"companyId" db:"company_id"`
|
||||
Role string `json:"role" db:"role"` // companyAdmin, recruiter
|
||||
Role string `json:"role" db:"role"` // admin, recruiter
|
||||
Permissions JSONMap `json:"permissions,omitempty" db:"permissions"`
|
||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ func (s *AdminService) ListCandidates(ctx context.Context) ([]dto.Candidate, dto
|
|||
query := `
|
||||
SELECT id, full_name, email, phone, city, state, title, experience, bio, skills, avatar_url, created_at
|
||||
FROM users
|
||||
WHERE role = 'jobSeeker'
|
||||
WHERE role = 'candidate'
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
|
||||
identifier VARCHAR(100) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(20) NOT NULL CHECK (role IN ('superadmin', 'companyAdmin', 'recruiter', 'jobSeeker')),
|
||||
role VARCHAR(20) NOT NULL CHECK (role IN ('superadmin', 'admin', 'recruiter', 'candidate')),
|
||||
|
||||
-- Personal Info
|
||||
full_name VARCHAR(255),
|
||||
|
|
@ -32,4 +32,4 @@ CREATE INDEX idx_users_active ON users(active);
|
|||
-- Comments for documentation
|
||||
COMMENT ON TABLE users IS 'Stores all system users across all roles';
|
||||
COMMENT ON COLUMN users.identifier IS 'Username for login (NOT email)';
|
||||
COMMENT ON COLUMN users.role IS 'User role: superadmin, companyAdmin, recruiter, or jobSeeker';
|
||||
COMMENT ON COLUMN users.role IS 'User role: superadmin, admin, recruiter, or candidate';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS user_companies (
|
|||
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
|
||||
user_id UUID NOT NULL,
|
||||
company_id UUID NOT NULL,
|
||||
role VARCHAR(20) NOT NULL CHECK (role IN ('companyAdmin', 'recruiter')),
|
||||
role VARCHAR(20) NOT NULL CHECK (role IN ('admin', 'recruiter')),
|
||||
permissions JSONB, -- Optional granular permissions
|
||||
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
|
@ -25,5 +25,5 @@ CREATE INDEX idx_user_companies_role ON user_companies(role);
|
|||
|
||||
-- Comments
|
||||
COMMENT ON TABLE user_companies IS 'Multi-tenant pivot: links users to companies with specific roles';
|
||||
COMMENT ON COLUMN user_companies.role IS 'Role within this company: companyAdmin or recruiter';
|
||||
COMMENT ON COLUMN user_companies.role IS 'Role within this company: admin or recruiter';
|
||||
COMMENT ON COLUMN user_companies.permissions IS 'Optional JSON object for granular permissions per company';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
CREATE TABLE IF NOT EXISTS password_resets (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
token VARCHAR(255) UNIQUE NOT NULL,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
used BOOLEAN DEFAULT false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
DROP TABLE IF EXISTS ticket_messages;
|
||||
DROP TABLE IF EXISTS tickets;
|
||||
|
||||
CREATE TABLE tickets (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
|
|
|||
18
docs/API.md
18
docs/API.md
|
|
@ -18,9 +18,9 @@ Complete API reference with routes, permissions, and modules.
|
|||
| Role | Code | Level | Description |
|
||||
|------|------|-------|-------------|
|
||||
| **SuperAdmin** | `superadmin` | 0 | Platform administrator |
|
||||
| **CompanyAdmin** | `companyAdmin` | 1 | Company administrator |
|
||||
| **Admin** | `admin` | 1 | Company administrator |
|
||||
| **Recruiter** | `recruiter` | 2 | Job poster |
|
||||
| **JobSeeker** | `jobSeeker` | 3 | Candidate |
|
||||
| **Candidate** | `candidate` | 3 | Candidate |
|
||||
| **Guest** | - | - | No authentication |
|
||||
|
||||
---
|
||||
|
|
@ -124,7 +124,7 @@ POST /api/v1/users
|
|||
```
|
||||
| Field | Auth | Roles | Description |
|
||||
|-------|------|-------|-------------|
|
||||
| Protected | ✅ | superadmin, companyAdmin | Create new user |
|
||||
| Protected | ✅ | superadmin, admin | Create new user |
|
||||
|
||||
### Update User
|
||||
```http
|
||||
|
|
@ -186,7 +186,7 @@ POST /api/v1/jobs
|
|||
```
|
||||
| Field | Auth | Roles | Description |
|
||||
|-------|------|-------|-------------|
|
||||
| Public* | ❌ | companyAdmin, recruiter | Create new job posting |
|
||||
| Public* | ❌ | admin, recruiter | Create new job posting |
|
||||
|
||||
### Update Job
|
||||
```http
|
||||
|
|
@ -194,7 +194,7 @@ PUT /api/v1/jobs/{id}
|
|||
```
|
||||
| Field | Auth | Roles | Description |
|
||||
|-------|------|-------|-------------|
|
||||
| Public* | ❌ | companyAdmin, recruiter | Update job posting |
|
||||
| Public* | ❌ | admin, recruiter | Update job posting |
|
||||
|
||||
### Delete Job
|
||||
```http
|
||||
|
|
@ -202,7 +202,7 @@ DELETE /api/v1/jobs/{id}
|
|||
```
|
||||
| Field | Auth | Roles | Description |
|
||||
|-------|------|-------|-------------|
|
||||
| Public* | ❌ | companyAdmin, recruiter | Delete job posting |
|
||||
| Public* | ❌ | admin, recruiter | Delete job posting |
|
||||
|
||||
### List Jobs for Moderation
|
||||
```http
|
||||
|
|
@ -258,7 +258,7 @@ GET /api/v1/applications
|
|||
```
|
||||
| Field | Auth | Roles | Description |
|
||||
|-------|------|-------|-------------|
|
||||
| Public | ❌ | companyAdmin, recruiter | List applications |
|
||||
| Public | ❌ | admin, recruiter | List applications |
|
||||
|
||||
### Get Application by ID
|
||||
```http
|
||||
|
|
@ -266,7 +266,7 @@ GET /api/v1/applications/{id}
|
|||
```
|
||||
| Field | Auth | Roles | Description |
|
||||
|-------|------|-------|-------------|
|
||||
| Public | ❌ | companyAdmin, recruiter | Get application details |
|
||||
| Public | ❌ | admin, recruiter | Get application details |
|
||||
|
||||
### Update Application Status
|
||||
```http
|
||||
|
|
@ -274,7 +274,7 @@ PUT /api/v1/applications/{id}/status
|
|||
```
|
||||
| Field | Auth | Roles | Description |
|
||||
|-------|------|-------|-------------|
|
||||
| Public | ❌ | companyAdmin, recruiter | Update application status |
|
||||
| Public | ❌ | admin, recruiter | Update application status |
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ This document details the security layers, authentication methods, and role-base
|
|||
| Method | Route | Description | Notes |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `POST` | `/api/v1/auth/login` | User Login | Returns JWT + Cookie |
|
||||
| `POST` | `/api/v1/auth/register` | Candidate Register | Creates `jobSeeker` user |
|
||||
| `POST` | `/api/v1/companies` | Company Register | Creates company + `companyAdmin` |
|
||||
| `POST` | `/api/v1/auth/register` | Candidate Register | Creates `candidate` user |
|
||||
| `POST` | `/api/v1/companies` | Company Register | Creates company + `admin` |
|
||||
| `GET` | `/api/v1/jobs` | List Jobs | Public search/list |
|
||||
| `GET` | `/api/v1/jobs/{id}` | Get Job | Public details |
|
||||
| `GET` | `/docs/*` | Swagger UI | API Documentation |
|
||||
|
|
@ -47,7 +47,7 @@ This document details the security layers, authentication methods, and role-base
|
|||
| `DELETE` | `/api/v1/storage/files` | Delete S3 File |
|
||||
|
||||
### 🟠 Recruiter / CompanyAdmin Routes
|
||||
**Requirement**: Role `companyAdmin` OR `recruiter`.
|
||||
**Requirement**: Role `admin` OR `recruiter`.
|
||||
|
||||
| Method | Route | Description |
|
||||
| :--- | :--- | :--- |
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ CREATE TABLE users (
|
|||
id SERIAL PRIMARY KEY,
|
||||
identifier VARCHAR(100) UNIQUE NOT NULL, -- Login (username/email)
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(20) NOT NULL, -- 'superadmin'|'companyAdmin'|'recruiter'|'jobSeeker'
|
||||
role VARCHAR(20) NOT NULL, -- 'superadmin'|'admin'|'recruiter'|'candidate'
|
||||
|
||||
-- Profile
|
||||
full_name VARCHAR(255),
|
||||
|
|
@ -190,9 +190,9 @@ CREATE TABLE users (
|
|||
|
||||
**Roles:**
|
||||
- `superadmin` - Platform administrator
|
||||
- `companyAdmin` - Company administrator
|
||||
- `admin` - Company administrator
|
||||
- `recruiter` - Job poster/recruiter
|
||||
- `jobSeeker` - Candidate/job seeker
|
||||
- `candidate` - Candidate/job seeker
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -544,7 +544,7 @@ CREATE TABLE user_companies (
|
|||
id SERIAL PRIMARY KEY,
|
||||
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
company_id INT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
|
||||
role VARCHAR(30) DEFAULT 'recruiter', -- 'companyAdmin'|'recruiter'
|
||||
role VARCHAR(30) DEFAULT 'recruiter', -- 'admin'|'recruiter'
|
||||
permissions JSONB,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, company_id)
|
||||
|
|
|
|||
|
|
@ -33,11 +33,11 @@ export default function DashboardPage() {
|
|||
return <AdminDashboardContent />
|
||||
}
|
||||
|
||||
if (user.role === "company" || user.roles?.includes("companyAdmin")) {
|
||||
if (user.role === "company" || user.roles?.includes("admin")) {
|
||||
return <CompanyDashboardContent user={user} />
|
||||
}
|
||||
|
||||
if (user.role === "candidate" || user.roles?.includes("jobSeeker")) {
|
||||
if (user.role === "candidate" || user.roles?.includes("candidate")) {
|
||||
return <CandidateDashboardContent />
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export default function AdminUsersPage() {
|
|||
name: "",
|
||||
email: "",
|
||||
password: "",
|
||||
role: "jobSeeker",
|
||||
role: "candidate",
|
||||
})
|
||||
const [editFormData, setEditFormData] = useState({
|
||||
name: "",
|
||||
|
|
@ -85,7 +85,7 @@ export default function AdminUsersPage() {
|
|||
await usersApi.create(formData)
|
||||
toast.success("User created successfully!")
|
||||
setIsDialogOpen(false)
|
||||
setFormData({ name: "", email: "", password: "", role: "jobSeeker" })
|
||||
setFormData({ name: "", email: "", password: "", role: "candidate" })
|
||||
setPage(1)
|
||||
loadUsers(1)
|
||||
} catch (error) {
|
||||
|
|
@ -158,18 +158,16 @@ export default function AdminUsersPage() {
|
|||
const getRoleBadge = (role: string) => {
|
||||
const labels: Record<string, string> = {
|
||||
superadmin: "Super Admin",
|
||||
companyAdmin: "Company admin",
|
||||
admin: "Company Admin",
|
||||
recruiter: "Recruiter",
|
||||
jobSeeker: "Candidate",
|
||||
admin: "Admin",
|
||||
candidate: "Candidate",
|
||||
company: "Company"
|
||||
}
|
||||
const colors: Record<string, "default" | "secondary" | "destructive" | "outline"> = {
|
||||
superadmin: "destructive",
|
||||
companyAdmin: "default",
|
||||
admin: "default",
|
||||
recruiter: "secondary",
|
||||
jobSeeker: "outline",
|
||||
admin: "destructive",
|
||||
candidate: "outline",
|
||||
company: "default"
|
||||
}
|
||||
const label = labels[role] || role || "User"
|
||||
|
|
@ -239,9 +237,9 @@ export default function AdminUsersPage() {
|
|||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="superadmin">Super Admin</SelectItem>
|
||||
<SelectItem value="companyAdmin">Company admin</SelectItem>
|
||||
<SelectItem value="admin">Company admin</SelectItem>
|
||||
<SelectItem value="recruiter">Recruiter</SelectItem>
|
||||
<SelectItem value="jobSeeker">Candidate</SelectItem>
|
||||
<SelectItem value="candidate">Candidate</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
|
@ -307,7 +305,7 @@ export default function AdminUsersPage() {
|
|||
<CardHeader className="pb-3">
|
||||
<CardDescription>Admins (page)</CardDescription>
|
||||
<CardTitle className="text-3xl">
|
||||
{users.filter((u) => u.role === "superadmin" || u.role === "companyAdmin").length}
|
||||
{users.filter((u) => u.role === "superadmin" || u.role === "admin" || u.role === "admin").length}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
|
@ -320,7 +318,7 @@ export default function AdminUsersPage() {
|
|||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardDescription>Candidates (page)</CardDescription>
|
||||
<CardTitle className="text-3xl">{users.filter((u) => u.role === "jobSeeker").length}</CardTitle>
|
||||
<CardTitle className="text-3xl">{users.filter((u) => u.role === "candidate" || u.role === "candidate").length}</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -44,9 +44,11 @@ export async function login(
|
|||
// Note: The backend returns roles as an array of strings. The frontend expects a single 'role' or we need to adapt.
|
||||
// For now we map the first role or main role to the 'role' field.
|
||||
let userRole: "candidate" | "admin" | "company" = "candidate";
|
||||
if (data.user.roles.includes("superadmin") || data.user.roles.includes("admin") || data.user.roles.includes("ADMIN") || data.user.roles.includes("SUPERADMIN")) {
|
||||
// Check for SuperAdmin (Platform Admin)
|
||||
if (data.user.roles.includes("superadmin") || data.user.roles.includes("SUPERADMIN")) {
|
||||
userRole = "admin";
|
||||
} else if (data.user.roles.includes("companyAdmin") || data.user.roles.includes("recruiter")) {
|
||||
// Check for Company Admin (now called 'admin') or Recruiter
|
||||
} else if (data.user.roles.includes("admin") || data.user.roles.includes("recruiter")) {
|
||||
userRole = "company";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,19 +14,26 @@ async function resetDatabase() {
|
|||
console.log('🗑️ Resetting database...');
|
||||
|
||||
try {
|
||||
// Drop all tables in reverse order (respecting foreign keys)
|
||||
await pool.query('DROP TABLE IF EXISTS password_resets CASCADE');
|
||||
await pool.query('DROP TABLE IF EXISTS favorite_jobs CASCADE');
|
||||
await pool.query('DROP TABLE IF EXISTS applications CASCADE');
|
||||
await pool.query('DROP TABLE IF EXISTS jobs CASCADE');
|
||||
await pool.query('DROP TABLE IF EXISTS user_companies CASCADE');
|
||||
await pool.query('DROP TABLE IF EXISTS companies CASCADE');
|
||||
await pool.query('DROP TABLE IF EXISTS users CASCADE');
|
||||
await pool.query('DROP TABLE IF EXISTS cities CASCADE');
|
||||
await pool.query('DROP TABLE IF EXISTS regions CASCADE');
|
||||
await pool.query('DROP TABLE IF EXISTS prefectures CASCADE'); // Legacy drop
|
||||
// Dynamic drop: Fetch all tables in public schema and drop them
|
||||
// This avoids "must be owner of schema public" error by operating on tables directly
|
||||
console.log('🔍 Finding tables to drop...');
|
||||
const tablesResult = await pool.query(`
|
||||
SELECT tablename
|
||||
FROM pg_tables
|
||||
WHERE schemaname = 'public'
|
||||
`);
|
||||
|
||||
console.log('✅ All tables dropped successfully');
|
||||
if (tablesResult.rows.length > 0) {
|
||||
const tables = tablesResult.rows.map(row => row.tablename);
|
||||
console.log(`🔥 Dropping ${tables.length} tables: ${tables.join(', ')}`);
|
||||
|
||||
// Construct a single DROP statement for all tables (CASCADE handles dependencies)
|
||||
const tableList = tables.map(t => `"${t}"`).join(', ');
|
||||
await pool.query(`DROP TABLE IF EXISTS ${tableList} CASCADE`);
|
||||
console.log('✅ All public tables dropped successfully.');
|
||||
} else {
|
||||
console.log('ℹ️ No tables found to drop IN public schema.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error resetting database:', error.message);
|
||||
throw error;
|
||||
|
|
|
|||
|
|
@ -80,7 +80,12 @@ export async function seedJobs() {
|
|||
let totalJobs = 0;
|
||||
|
||||
try {
|
||||
// Process companies in chunks to avoid overwhelming the DB
|
||||
for (const company of companies) {
|
||||
const jobValues = [];
|
||||
const jobParams = [];
|
||||
let paramCounter = 1;
|
||||
|
||||
// Generate 25 jobs per company
|
||||
for (let i = 0; i < 25; i++) {
|
||||
const template = jobTemplates[i % jobTemplates.length];
|
||||
|
|
@ -98,13 +103,8 @@ export async function seedJobs() {
|
|||
const currency = currencies[i % currencies.length];
|
||||
const salaryType = salaryTypes[i % salaryTypes.length];
|
||||
|
||||
// jobs.id is SERIAL - let DB auto-generate
|
||||
await pool.query(`
|
||||
INSERT INTO jobs (company_id, created_by, title, description,
|
||||
salary_min, salary_max, salary_type, currency, employment_type, working_hours,
|
||||
location, requirements, benefits, visa_support, language_level, status, work_mode)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
||||
`, [
|
||||
// Prepare params for bulk insert
|
||||
jobParams.push(
|
||||
company.id,
|
||||
seedUserId,
|
||||
title,
|
||||
|
|
@ -122,11 +122,28 @@ export async function seedJobs() {
|
|||
'beginner',
|
||||
'open',
|
||||
workMode
|
||||
]);
|
||||
);
|
||||
|
||||
// ($1, $2, $3, ...), ($18, $19, ...)
|
||||
const placeHolders = [];
|
||||
for (let k = 0; k < 17; k++) {
|
||||
placeHolders.push(`$${paramCounter++}`);
|
||||
}
|
||||
jobValues.push(`(${placeHolders.join(',')})`);
|
||||
|
||||
totalJobs++;
|
||||
}
|
||||
|
||||
// Bulk Insert for this company
|
||||
if (jobValues.length > 0) {
|
||||
const query = `
|
||||
INSERT INTO jobs (company_id, created_by, title, description,
|
||||
salary_min, salary_max, salary_type, currency, employment_type, working_hours,
|
||||
location, requirements, benefits, visa_support, language_level, status, work_mode)
|
||||
VALUES ${jobValues.join(',')}
|
||||
`;
|
||||
await pool.query(query, jobParams);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✓ ${totalJobs} jobs seeded across ${companies.length} companies`);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export async function seedNotifications() {
|
|||
createdAt.setHours(createdAt.getHours() - hoursAgo);
|
||||
|
||||
await pool.query(`
|
||||
INSERT INTO notifications (id, user_id, title, message, type, is_read, created_at, updated_at)
|
||||
INSERT INTO notifications (id, user_id, title, message, type, read_at, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (id) DO NOTHING
|
||||
`, [
|
||||
|
|
@ -117,7 +117,7 @@ export async function seedNotifications() {
|
|||
template.title,
|
||||
template.message,
|
||||
template.type,
|
||||
template.is_read,
|
||||
template.is_read ? new Date() : null,
|
||||
createdAt,
|
||||
createdAt
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -40,13 +40,13 @@ export async function seedUsers() {
|
|||
console.log(' ✓ SuperAdmin created (superadmin)');
|
||||
|
||||
// 2. Create Company Admins
|
||||
const companyAdmins = [
|
||||
{ identifier: 'takeshi_yamamoto', fullName: 'Takeshi Yamamoto', company: 'TechCorp', email: 'takeshi@techcorp.com', pass: 'Takeshi@2025', roles: ['companyAdmin'] },
|
||||
{ identifier: 'kenji', fullName: 'Kenji Tanaka', company: 'AppMakers', email: 'kenji@appmakers.mobile', pass: 'Takeshi@2025', roles: ['companyAdmin'] },
|
||||
const admins = [
|
||||
{ identifier: 'takeshi_yamamoto', fullName: 'Takeshi Yamamoto', company: 'TechCorp', email: 'takeshi@techcorp.com', pass: 'Takeshi@2025', roles: ['admin'] },
|
||||
{ identifier: 'kenji', fullName: 'Kenji Tanaka', company: 'AppMakers', email: 'kenji@appmakers.mobile', pass: 'Takeshi@2025', roles: ['admin'] },
|
||||
{ identifier: 'maria_santos', fullName: 'Maria Santos', company: 'DesignHub', email: 'maria@designhub.com', pass: 'User@2025', roles: ['recruiter'] }
|
||||
];
|
||||
|
||||
for (const admin of companyAdmins) {
|
||||
for (const admin of admins) {
|
||||
const hash = await bcrypt.hash(admin.pass + PASSWORD_PEPPER, 10);
|
||||
const tenantId = companyMap[admin.company];
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ export async function seedUsers() {
|
|||
|
||||
const result = await pool.query(`
|
||||
INSERT INTO users (identifier, password_hash, role, full_name, email, name, status)
|
||||
VALUES ($1, $2, 'jobSeeker', $3, $4, $5, 'active')
|
||||
VALUES ($1, $2, 'candidate', $3, $4, $5, 'active')
|
||||
ON CONFLICT (identifier) DO UPDATE SET password_hash = EXCLUDED.password_hash
|
||||
RETURNING id
|
||||
`, [cand.identifier, hash, cand.fullName, cand.email, cand.fullName]);
|
||||
|
|
@ -178,7 +178,7 @@ export async function seedUsers() {
|
|||
INSERT INTO users (
|
||||
identifier, password_hash, role, full_name, email, name, phone,
|
||||
city, state, title, experience, skills, objective, bio, status
|
||||
) VALUES ($1, $2, 'jobSeeker', $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, 'active')
|
||||
) VALUES ($1, $2, 'candidate', $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, 'active')
|
||||
ON CONFLICT (identifier) DO UPDATE SET
|
||||
full_name = EXCLUDED.full_name,
|
||||
email = EXCLUDED.email,
|
||||
|
|
|
|||
Loading…
Reference in a new issue