fix(backoffice): use string IDs for companies/jobs/users, fix audit log, ensure real tags support

This commit is contained in:
Tiago Yamamoto 2025-12-24 19:43:49 -03:00
parent 9784e959e4
commit 4eae018a25
5 changed files with 23 additions and 28 deletions

View file

@ -760,19 +760,14 @@ func (h *CoreHandlers) Me(w http.ResponseWriter, r *http.Request) {
return
}
var userID int
var userID string
switch v := userIDVal.(type) {
case int:
userID = v
userID = strconv.Itoa(v)
case string:
var err error
userID, err = strconv.Atoi(v)
if err != nil {
http.Error(w, "Invalid User ID in context", http.StatusInternalServerError)
return
}
userID = v
case float64:
userID = int(v)
userID = strconv.Itoa(int(v))
}
user, err := h.adminService.GetUser(ctx, userID)

View file

@ -15,12 +15,12 @@ type LoginResponse struct {
Token string `json:"token"`
User UserInfo `json:"user"`
Companies []CompanyInfo `json:"companies,omitempty"`
ActiveCompanyID *int `json:"activeCompanyId,omitempty"`
ActiveCompanyID *string `json:"activeCompanyId,omitempty"`
}
// UserInfo represents basic user information in responses
type UserInfo struct {
ID int `json:"id"`
ID string `json:"id"`
Identifier string `json:"identifier"`
Role string `json:"role"`
FullName string `json:"fullName"`
@ -29,7 +29,7 @@ type UserInfo struct {
// CompanyInfo represents basic company information
type CompanyInfo struct {
ID int `json:"id"`
ID string `json:"id"`
Name string `json:"name"`
Role string `json:"role"` // Role in this company (admin or recruiter)
}
@ -49,7 +49,7 @@ type RegisterRequest struct {
// User represents a generic user profile
type User struct {
ID int `json:"id"`
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Role string `json:"role"`

View file

@ -525,7 +525,7 @@ func (s *AdminService) getTagByID(ctx context.Context, id int) (*models.Tag, err
}
// GetUser fetches a user by ID
func (s *AdminService) GetUser(ctx context.Context, id int) (*dto.User, error) {
func (s *AdminService) GetUser(ctx context.Context, id string) (*dto.User, error) {
query := `
SELECT id, name, email, role, created_at
FROM users WHERE id = $1
@ -540,7 +540,7 @@ func (s *AdminService) GetUser(ctx context.Context, id int) (*dto.User, error) {
}
// GetCompanyByUserID fetches the company associated with a user
func (s *AdminService) GetCompanyByUserID(ctx context.Context, userID int) (*models.Company, error) {
func (s *AdminService) GetCompanyByUserID(ctx context.Context, userID string) (*models.Company, error) {
// First, try to find company where this user is admin
// Assuming users table has company_id or companies table has admin_email
// Let's check if 'users' has company_id column via error or assume architecture.
@ -568,7 +568,7 @@ func (s *AdminService) GetCompanyByUserID(ctx context.Context, userID int) (*mod
WHERE u.id = $1
`
if err2 := s.DB.QueryRowContext(ctx, query2, userID).Scan(&c.ID, &c.Name, &c.Slug, &c.Active, &c.Verified); err2 != nil {
return nil, fmt.Errorf("company not found for user %d", userID)
return nil, fmt.Errorf("company not found for user %s", userID)
}
}
return &c, nil

View file

@ -91,7 +91,7 @@ export default function BackofficePage() {
}
}
const handleApproveCompany = async (companyId: number) => {
const handleApproveCompany = async (companyId: string) => {
try {
await adminCompaniesApi.updateStatus(companyId, { verified: true })
toast.success("Company approved")
@ -102,7 +102,7 @@ export default function BackofficePage() {
}
}
const handleDeactivateCompany = async (companyId: number) => {
const handleDeactivateCompany = async (companyId: string) => {
try {
await adminCompaniesApi.updateStatus(companyId, { active: false })
toast.success("Company deactivated")
@ -113,7 +113,7 @@ export default function BackofficePage() {
}
}
const handleJobStatus = async (jobId: number, status: string) => {
const handleJobStatus = async (jobId: string, status: string) => {
try {
await adminJobsApi.updateStatus(jobId, status)
toast.success("Job status updated")
@ -124,7 +124,7 @@ export default function BackofficePage() {
}
}
const handleDuplicateJob = async (jobId: number) => {
const handleDuplicateJob = async (jobId: string) => {
try {
await adminJobsApi.duplicate(jobId)
toast.success("Job duplicated as draft")
@ -283,11 +283,11 @@ export default function BackofficePage() {
)}
</TableCell>
<TableCell className="text-right space-x-2">
<Button size="sm" variant="outline" onClick={() => handleApproveCompany(Number(company.id))}>
<Button size="sm" variant="outline" onClick={() => handleApproveCompany(company.id)}>
<CheckCircle className="h-4 w-4 mr-2" />
Aprovar
</Button>
<Button size="sm" variant="destructive" onClick={() => handleDeactivateCompany(Number(company.id))}>
<Button size="sm" variant="destructive" onClick={() => handleDeactivateCompany(company.id)}>
<XCircle className="h-4 w-4 mr-2" />
Desativar
</Button>

View file

@ -87,7 +87,7 @@ export interface AdminCompany {
}
export interface AdminJob {
id: number;
id: string;
title: string;
companyName: string;
status: string;
@ -116,7 +116,7 @@ export interface AdminTag {
}
export interface AdminCandidate {
id: number;
id: string;
name: string;
email: string;
phone: string;
@ -218,12 +218,12 @@ export const adminJobsApi = {
return apiRequest<{ data: AdminJob[]; pagination: any }>(`/api/v1/jobs/moderation?${query.toString()}`);
},
updateStatus: (id: number, status: string) =>
updateStatus: (id: string, status: string) =>
apiRequest<void>(`/api/v1/jobs/${id}/status`, {
method: "PATCH",
body: JSON.stringify({ status }),
}),
duplicate: (id: number) =>
duplicate: (id: string) =>
apiRequest<void>(`/api/v1/jobs/${id}/duplicate`, {
method: "POST",
}),
@ -275,14 +275,14 @@ export const adminCompaniesApi = {
body: JSON.stringify(data),
});
},
updateStatus: (id: number, data: { active?: boolean; verified?: boolean }) => {
updateStatus: (id: string, data: { active?: boolean; verified?: boolean }) => {
logCrudAction("update", "admin/companies", { id, ...data });
return apiRequest<void>(`/api/v1/companies/${id}/status`, {
method: "PATCH",
body: JSON.stringify(data),
});
},
delete: (id: number) => {
delete: (id: string) => {
logCrudAction("delete", "admin/companies", { id });
return apiRequest<void>(`/api/v1/companies/${id}`, {
method: "DELETE"