feat: SEO optimization and dynamic jobs API integration
Backend: - Add Swagger annotations to all job handlers (GET, POST, PUT, DELETE) - Clean up job handler code Frontend: - Expand api.ts with ApiJob types, pagination, and transform function - Update footer with 'Vagas por Tecnologia' SEO links - Add robots.txt with crawler directives - Add sitemap.xml with main pages and job URLs - Change branding to GoHorse Jobs
This commit is contained in:
parent
8856357acd
commit
a4abcf8e05
12 changed files with 284 additions and 38 deletions
|
|
@ -17,6 +17,18 @@ func NewJobHandler(service *services.JobService) *JobHandler {
|
||||||
return &JobHandler{Service: service}
|
return &JobHandler{Service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetJobs godoc
|
||||||
|
// @Summary List all jobs
|
||||||
|
// @Description Get a paginated list of job postings with optional filters
|
||||||
|
// @Tags Jobs
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "Page number (default: 1)"
|
||||||
|
// @Param limit query int false "Items per page (default: 10, max: 100)"
|
||||||
|
// @Param companyId query int false "Filter by company ID"
|
||||||
|
// @Success 200 {object} dto.PaginatedResponse
|
||||||
|
// @Failure 500 {string} string "Internal Server Error"
|
||||||
|
// @Router /jobs [get]
|
||||||
func (h *JobHandler) GetJobs(w http.ResponseWriter, r *http.Request) {
|
func (h *JobHandler) GetJobs(w http.ResponseWriter, r *http.Request) {
|
||||||
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
||||||
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||||
|
|
@ -51,6 +63,17 @@ func (h *JobHandler) GetJobs(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateJob godoc
|
||||||
|
// @Summary Create a new job
|
||||||
|
// @Description Create a new job posting
|
||||||
|
// @Tags Jobs
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param job body dto.CreateJobRequest true "Job data"
|
||||||
|
// @Success 201 {object} models.Job
|
||||||
|
// @Failure 400 {string} string "Bad Request"
|
||||||
|
// @Failure 500 {string} string "Internal Server Error"
|
||||||
|
// @Router /jobs [post]
|
||||||
func (h *JobHandler) CreateJob(w http.ResponseWriter, r *http.Request) {
|
func (h *JobHandler) CreateJob(w http.ResponseWriter, r *http.Request) {
|
||||||
var req dto.CreateJobRequest
|
var req dto.CreateJobRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
|
@ -71,29 +94,19 @@ func (h *JobHandler) CreateJob(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(job)
|
json.NewEncoder(w).Encode(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetJobByID godoc
|
||||||
|
// @Summary Get job by ID
|
||||||
|
// @Description Get a single job posting by its ID
|
||||||
|
// @Tags Jobs
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Job ID"
|
||||||
|
// @Success 200 {object} models.Job
|
||||||
|
// @Failure 400 {string} string "Bad Request"
|
||||||
|
// @Failure 404 {string} string "Not Found"
|
||||||
|
// @Router /jobs/{id} [get]
|
||||||
func (h *JobHandler) GetJobByID(w http.ResponseWriter, r *http.Request) {
|
func (h *JobHandler) GetJobByID(w http.ResponseWriter, r *http.Request) {
|
||||||
idStr := r.PathValue("id") // Go 1.22+ routing
|
idStr := r.PathValue("id") // Go 1.22+ routing
|
||||||
if idStr == "" {
|
|
||||||
// Fallback for older Go versions or if not using PathValue compatible mux
|
|
||||||
// But let's assume standard mux or we might need to parse URL
|
|
||||||
// For now, let's assume we can get it from context or URL
|
|
||||||
// If using standard http.ServeMux in Go 1.22, PathValue works.
|
|
||||||
// If not, we might need a helper.
|
|
||||||
// Let's assume standard mux with Go 1.22 for now as it's modern.
|
|
||||||
// If not, we'll fix it.
|
|
||||||
// Actually, let's check go.mod version.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait, I should check go.mod version to be safe.
|
|
||||||
// But let's write standard code.
|
|
||||||
|
|
||||||
// Assuming we use a router that puts params in context or we parse URL.
|
|
||||||
// Since I am editing router.go later, I can ensure we use Go 1.22 patterns or a library.
|
|
||||||
// The existing router.go used `mux.HandleFunc("/jobs", ...)` which suggests standard lib.
|
|
||||||
|
|
||||||
// Let's try to parse from URL path if PathValue is not available (it is in Go 1.22).
|
|
||||||
// I'll use a helper to extract ID from URL for now to be safe if I'm not sure about Go version.
|
|
||||||
// But `r.PathValue` is the way forward.
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(idStr)
|
id, err := strconv.Atoi(idStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -111,6 +124,18 @@ func (h *JobHandler) GetJobByID(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(job)
|
json.NewEncoder(w).Encode(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateJob godoc
|
||||||
|
// @Summary Update a job
|
||||||
|
// @Description Update an existing job posting
|
||||||
|
// @Tags Jobs
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Job ID"
|
||||||
|
// @Param job body dto.UpdateJobRequest true "Updated job data"
|
||||||
|
// @Success 200 {object} models.Job
|
||||||
|
// @Failure 400 {string} string "Bad Request"
|
||||||
|
// @Failure 500 {string} string "Internal Server Error"
|
||||||
|
// @Router /jobs/{id} [put]
|
||||||
func (h *JobHandler) UpdateJob(w http.ResponseWriter, r *http.Request) {
|
func (h *JobHandler) UpdateJob(w http.ResponseWriter, r *http.Request) {
|
||||||
idStr := r.PathValue("id")
|
idStr := r.PathValue("id")
|
||||||
id, err := strconv.Atoi(idStr)
|
id, err := strconv.Atoi(idStr)
|
||||||
|
|
@ -135,6 +160,17 @@ func (h *JobHandler) UpdateJob(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(job)
|
json.NewEncoder(w).Encode(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteJob godoc
|
||||||
|
// @Summary Delete a job
|
||||||
|
// @Description Delete a job posting
|
||||||
|
// @Tags Jobs
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Job ID"
|
||||||
|
// @Success 204 "No Content"
|
||||||
|
// @Failure 400 {string} string "Bad Request"
|
||||||
|
// @Failure 500 {string} string "Internal Server Error"
|
||||||
|
// @Router /jobs/{id} [delete]
|
||||||
func (h *JobHandler) DeleteJob(w http.ResponseWriter, r *http.Request) {
|
func (h *JobHandler) DeleteJob(w http.ResponseWriter, r *http.Request) {
|
||||||
idStr := r.PathValue("id")
|
idStr := r.PathValue("id")
|
||||||
id, err := strconv.Atoi(idStr)
|
id, err := strconv.Atoi(idStr)
|
||||||
|
|
|
||||||
15
frontend/public/robots.txt
Normal file
15
frontend/public/robots.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Sitemap
|
||||||
|
Sitemap: https://gohorsejobs.com/sitemap.xml
|
||||||
|
|
||||||
|
# Crawl-delay for polite bots
|
||||||
|
Crawl-delay: 1
|
||||||
|
|
||||||
|
# Disallow admin/dashboard areas from indexing
|
||||||
|
Disallow: /dashboard/
|
||||||
|
Disallow: /api/
|
||||||
|
Disallow: /login
|
||||||
|
Disallow: /cadastro/
|
||||||
82
frontend/public/sitemap.xml
Normal file
82
frontend/public/sitemap.xml
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<!-- Main Pages -->
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>1.0</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/vagas</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>0.9</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/sobre</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.6</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/contato</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.6</priority>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
<!-- Vagas por Tecnologia -->
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/vagas?tech=python</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/vagas?tech=react</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/vagas?tech=nodejs</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/vagas?tech=dados</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
<!-- Vagas por Tipo -->
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/vagas?type=remoto</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/vagas?type=full-time</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>0.7</priority>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
<!-- Legal -->
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/privacidade</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>yearly</changefreq>
|
||||||
|
<priority>0.3</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://gohorsejobs.com/termos</loc>
|
||||||
|
<lastmod>2025-12-14</lastmod>
|
||||||
|
<changefreq>yearly</changefreq>
|
||||||
|
<priority>0.3</priority>
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
|
|
@ -1,52 +1,76 @@
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { Briefcase } from "lucide-react"
|
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className="border-t border-border bg-muted/30 mt-24">
|
<footer className="border-t border-border bg-muted/30 mt-24">
|
||||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-5 gap-8">
|
||||||
<div className="col-span-1 md:col-span-2">
|
<div className="col-span-1 md:col-span-2">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<span className="text-xl font-semibold">GoHorse Jobs</span>
|
||||||
<span className="text-xl font-semibold">Portal de Empregos</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-muted-foreground text-sm leading-relaxed max-w-md">
|
<p className="text-muted-foreground text-sm leading-relaxed max-w-md">
|
||||||
Conectamos candidatos e empresas de forma rápida e direta. Encontre sua próxima oportunidade profissional.
|
Conectamos candidatos e empresas de forma rápida e direta. Encontre sua próxima oportunidade profissional em tecnologia.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold mb-4">Empresa</h3>
|
<h3 className="font-semibold mb-4">Vagas por Tecnologia</h3>
|
||||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||||
<li>
|
<li>
|
||||||
<Link href="#" className="hover:text-foreground transition-colors">
|
<Link href="/vagas?tech=python" className="hover:text-foreground transition-colors">
|
||||||
Sobre
|
Desenvolvedor Python
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link href="#" className="hover:text-foreground transition-colors">
|
<Link href="/vagas?tech=react" className="hover:text-foreground transition-colors">
|
||||||
Contato
|
Desenvolvedor React
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link href="#" className="hover:text-foreground transition-colors">
|
<Link href="/vagas?tech=dados" className="hover:text-foreground transition-colors">
|
||||||
Carreiras
|
Analista de Dados
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/vagas?type=remoto" className="hover:text-foreground transition-colors">
|
||||||
|
Vagas Remotas
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold mb-4">Privacidade e Termos</h3>
|
<h3 className="font-semibold mb-4">Empresa</h3>
|
||||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||||
<li>
|
<li>
|
||||||
<Link href="#" className="hover:text-foreground transition-colors">
|
<Link href="/sobre" className="hover:text-foreground transition-colors">
|
||||||
|
Sobre
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/contato" className="hover:text-foreground transition-colors">
|
||||||
|
Contato
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link href="/vagas" className="hover:text-foreground transition-colors">
|
||||||
|
Todas as Vagas
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold mb-4">Legal</h3>
|
||||||
|
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||||
|
<li>
|
||||||
|
<Link href="/privacidade" className="hover:text-foreground transition-colors">
|
||||||
Política de Privacidade
|
Política de Privacidade
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link href="#" className="hover:text-foreground transition-colors">
|
<Link href="/termos" className="hover:text-foreground transition-colors">
|
||||||
Termos de Uso
|
Termos de Uso
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -55,9 +79,10 @@ export function Footer() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-12 pt-8 border-t border-border text-center text-sm text-muted-foreground">
|
<div className="mt-12 pt-8 border-t border-border text-center text-sm text-muted-foreground">
|
||||||
<p>© 2025 Portal de Empregos. Todos os direitos reservados.</p>
|
<p>© 2025 GoHorse Jobs. Todos os direitos reservados.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,94 @@ export const companiesApi = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Jobs API (public)
|
// Jobs API (public)
|
||||||
|
export interface ApiJob {
|
||||||
|
id: number;
|
||||||
|
companyId: number;
|
||||||
|
createdBy: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
salaryMin?: number;
|
||||||
|
salaryMax?: number;
|
||||||
|
salaryType?: string;
|
||||||
|
employmentType?: string;
|
||||||
|
workingHours?: string;
|
||||||
|
location?: string;
|
||||||
|
regionId?: number;
|
||||||
|
cityId?: number;
|
||||||
|
requirements?: Record<string, unknown> | string[];
|
||||||
|
benefits?: Record<string, unknown> | string[];
|
||||||
|
visaSupport: boolean;
|
||||||
|
languageLevel?: string;
|
||||||
|
status: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
companyName?: string;
|
||||||
|
companyLogoUrl?: string;
|
||||||
|
regionName?: string;
|
||||||
|
cityName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedResponse<T> {
|
||||||
|
data: T[];
|
||||||
|
pagination: {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const jobsApi = {
|
export const jobsApi = {
|
||||||
list: () => apiRequest<unknown[]>("/jobs"),
|
list: (params?: { page?: number; limit?: number; companyId?: number }) => {
|
||||||
|
const query = new URLSearchParams();
|
||||||
|
if (params?.page) query.set('page', String(params.page));
|
||||||
|
if (params?.limit) query.set('limit', String(params.limit));
|
||||||
|
if (params?.companyId) query.set('companyId', String(params.companyId));
|
||||||
|
const queryStr = query.toString();
|
||||||
|
return apiRequest<PaginatedResponse<ApiJob>>(`/jobs${queryStr ? `?${queryStr}` : ''}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getById: (id: number) => apiRequest<ApiJob>(`/jobs/${id}`),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Transform API job to frontend Job format
|
||||||
|
export function transformApiJobToFrontend(apiJob: ApiJob): import('./types').Job {
|
||||||
|
// Format salary
|
||||||
|
let salary: string | undefined;
|
||||||
|
if (apiJob.salaryMin && apiJob.salaryMax) {
|
||||||
|
salary = `R$ ${apiJob.salaryMin.toLocaleString('pt-BR')} - R$ ${apiJob.salaryMax.toLocaleString('pt-BR')}`;
|
||||||
|
} else if (apiJob.salaryMin) {
|
||||||
|
salary = `A partir de R$ ${apiJob.salaryMin.toLocaleString('pt-BR')}`;
|
||||||
|
} else if (apiJob.salaryMax) {
|
||||||
|
salary = `Até R$ ${apiJob.salaryMax.toLocaleString('pt-BR')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine type
|
||||||
|
type JobType = 'full-time' | 'part-time' | 'contract' | 'Remoto' | 'Tempo Integral';
|
||||||
|
let type: JobType = 'Tempo Integral';
|
||||||
|
if (apiJob.employmentType === 'full-time') type = 'Tempo Integral';
|
||||||
|
else if (apiJob.employmentType === 'part-time') type = 'part-time';
|
||||||
|
else if (apiJob.employmentType === 'contract') type = 'contract';
|
||||||
|
else if (apiJob.location?.toLowerCase().includes('remoto')) type = 'Remoto';
|
||||||
|
|
||||||
|
// Extract requirements
|
||||||
|
const requirements: string[] = [];
|
||||||
|
if (apiJob.requirements) {
|
||||||
|
if (Array.isArray(apiJob.requirements)) {
|
||||||
|
requirements.push(...apiJob.requirements.map(String));
|
||||||
|
} else if (typeof apiJob.requirements === 'object') {
|
||||||
|
Object.values(apiJob.requirements).forEach(v => requirements.push(String(v)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: String(apiJob.id),
|
||||||
|
title: apiJob.title,
|
||||||
|
company: apiJob.companyName || 'Empresa',
|
||||||
|
location: apiJob.location || apiJob.cityName || 'Localização não informada',
|
||||||
|
type,
|
||||||
|
salary,
|
||||||
|
description: apiJob.description,
|
||||||
|
requirements: requirements.length > 0 ? requirements : ['Ver detalhes'],
|
||||||
|
postedAt: apiJob.createdAt?.split('T')[0] || new Date().toISOString().split('T')[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue