This commit is contained in:
2026-08-17 21:20:45 +02:00
parent 321f11e817
commit 6fcdc74843
157 changed files with 2895 additions and 7544 deletions
@@ -227,6 +227,7 @@ func (s *Server) handleV1GetProcess(w http.ResponseWriter, r *http.Request) {
v1Err(w, http.StatusInternalServerError, "internal_server_error", "Internal server error")
return
}
items = processing.ApplyV1SEOMetaPolicy(cid, items)
data := map[string]any{
"status": status,
"process_id": job.ID.String(),
@@ -245,8 +246,9 @@ func (s *Server) handleV1GetProcess(w http.ResponseWriter, r *http.Request) {
v1OK(w, http.StatusOK, data, nil)
case "FAILED":
errMsg := "Processing failed"
if job.Error != nil && *job.Error != "" {
errMsg = *job.Error
if job.Error != nil && strings.TrimSpace(*job.Error) != "" {
log.Printf("httpapi: v1 process job %s failed: %s", job.ID, redactForLog(*job.Error))
errMsg = publicV1JobError(*job.Error)
}
v1OK(w, http.StatusOK, map[string]any{
"status": status,
@@ -269,6 +271,99 @@ func (s *Server) handleV1GetProcess(w http.ResponseWriter, r *http.Request) {
}
}
func publicV1JobError(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" {
return "Processing failed"
}
out := processing.PublicV1Error(raw)
if strings.TrimSpace(out) == "" {
return "Processing failed"
}
return out
}
func presentV1Job(job processing.Job) map[string]any {
out := map[string]any{
"id": job.ID,
"status": job.Status,
"total_products": job.TotalProducts,
"processed_products": job.ProcessedProducts,
"processing_type": processing.ProcessingTypeForAPIResponse(job.ProcessingType),
"created_at": formatV1Timestamp(job.CreatedAt),
}
if job.StartedAt != nil {
out["started_at"] = formatV1Timestamp(*job.StartedAt)
}
if job.CompletedAt != nil {
out["completed_at"] = formatV1Timestamp(*job.CompletedAt)
}
if job.Error != nil && strings.TrimSpace(*job.Error) != "" {
out["error"] = publicV1JobError(*job.Error)
}
return out
}
func presentV1JobList(jobs []processing.Job) []any {
if len(jobs) == 0 {
return []any{}
}
formatted := processing.FormatListJobsResponse(jobs)
out := make([]any, len(formatted))
for i, row := range formatted {
base := presentV1Job(jobs[i])
raw, err := json.Marshal(row)
if err != nil {
out[i] = base
continue
}
var extra map[string]any
if err := json.Unmarshal(raw, &extra); err != nil {
out[i] = base
continue
}
for _, k := range []string{"sibling_job_ids", "job_count", "total_products_queued"} {
if v, ok := extra[k]; ok {
base[k] = v
}
}
out[i] = base
}
return out
}
// handleV1GetProcessJob serves GET /api/v1/process/{id} with a public job DTO
// (no current_step, step notes, or company_id). Dashboard GET stays on handleGetProcessingJob.
func (s *Server) handleV1GetProcessJob(w http.ResponseWriter, r *http.Request) {
cid, ok := CompanyIDFromContext(r.Context())
if !ok || cid == uuid.Nil {
Error(w, http.StatusUnauthorized, "unauthorized")
return
}
id, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
Error(w, http.StatusBadRequest, "invalid id")
return
}
job, err := s.getV1ProcessJob(r.Context(), cid, id)
if err != nil {
Error(w, http.StatusNotFound, "not found")
return
}
out := presentV1Job(job)
if processing.JobStatusIncludesProducts(job.Status) {
items, loadErr := s.loadV1ProcessJobItems(r.Context(), cid, id, job.ProcessingType)
if loadErr != nil {
Error(w, http.StatusInternalServerError, "load failed")
return
}
items = processing.ApplyV1SEOMetaPolicy(cid, items)
out["items"] = items
out["total_items"] = len(items)
}
JSON(w, http.StatusOK, out)
}
func (s *Server) ensureRawV1Items(ctx context.Context, companyID uuid.UUID, items []catalog.V1ProcessItem) ([]uuid.UUID, []catalog.EnsureRawResult, []string, error) {
if s != nil && s.testEnsureRawV1Items != nil {
return s.testEnsureRawV1Items(ctx, companyID, items)