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