Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/catalog"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/marketing"
|
||||
)
|
||||
|
||||
func (s *Server) marketingService() *marketing.Service {
|
||||
return &marketing.Service{Pool: s.Pool, Feeds: s.Feeds}
|
||||
}
|
||||
|
||||
func (s *Server) handleGetMarketingCalendar(w http.ResponseWriter, r *http.Request) {
|
||||
cid, _ := CompanyIDFromContext(r.Context())
|
||||
year := time.Now().UTC().Year()
|
||||
if y := r.URL.Query().Get("year"); y != "" {
|
||||
parsed, err := strconv.Atoi(y)
|
||||
if err != nil || parsed < 2000 || parsed > 2100 {
|
||||
Error(w, http.StatusBadRequest, "invalid year")
|
||||
return
|
||||
}
|
||||
year = parsed
|
||||
}
|
||||
prepared, err := s.marketingService().ListPreparedCampaigns(r.Context(), cid)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "list failed")
|
||||
return
|
||||
}
|
||||
JSON(w, http.StatusOK, map[string]any{
|
||||
"year": year,
|
||||
"presets": marketing.ListPresets(year),
|
||||
"prepared": prepared,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handlePrepareMarketingCalendar(w http.ResponseWriter, r *http.Request) {
|
||||
cid, _ := CompanyIDFromContext(r.Context())
|
||||
var body struct {
|
||||
PresetID string `json:"preset_id"`
|
||||
Year int `json:"year"`
|
||||
Format string `json:"format"`
|
||||
ForceNew bool `json:"force_new"`
|
||||
}
|
||||
if err := DecodeJSON(r, &body); err != nil {
|
||||
Error(w, http.StatusBadRequest, "invalid json")
|
||||
return
|
||||
}
|
||||
campaign, err := s.marketingService().PrepareCampaign(r.Context(), cid, marketing.PrepareInput{
|
||||
PresetID: marketing.PresetID(body.PresetID),
|
||||
Year: body.Year,
|
||||
Format: body.Format,
|
||||
ForceNew: body.ForceNew,
|
||||
})
|
||||
if err != nil {
|
||||
ClientOrLog(w, http.StatusBadRequest, "could not prepare campaign", err, marketing.ClientError)
|
||||
return
|
||||
}
|
||||
status := http.StatusOK
|
||||
if campaign.Created {
|
||||
status = http.StatusCreated
|
||||
}
|
||||
JSON(w, status, campaign)
|
||||
}
|
||||
|
||||
func (s *Server) handleListProductQuality(w http.ResponseWriter, r *http.Request) {
|
||||
cid, _ := CompanyIDFromContext(r.Context())
|
||||
limit, offset := ParseLimitOffset(r)
|
||||
var minScore *int
|
||||
if raw := r.URL.Query().Get("min_score"); raw != "" {
|
||||
n, err := strconv.Atoi(raw)
|
||||
if err != nil {
|
||||
Error(w, http.StatusBadRequest, "invalid min_score")
|
||||
return
|
||||
}
|
||||
minScore = &n
|
||||
}
|
||||
|
||||
f := catalog.ListFilter{
|
||||
Query: QuerySearch(r),
|
||||
Status: r.URL.Query().Get("status"),
|
||||
Category: r.URL.Query().Get("category"),
|
||||
FeedID: firstNonEmpty(r.URL.Query().Get("feed_id"), r.URL.Query().Get("feedId")),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}
|
||||
if f.Status == "" {
|
||||
f.Status = "completed"
|
||||
}
|
||||
|
||||
items, total, err := s.Catalog.ListProcessedProductsDetailed(r.Context(), cid, f)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "list failed")
|
||||
return
|
||||
}
|
||||
|
||||
out := make([]map[string]any, 0, len(items))
|
||||
for _, item := range items {
|
||||
q := marketing.ScoreFromProductMap(item)
|
||||
if minScore != nil && q.Score < *minScore {
|
||||
continue
|
||||
}
|
||||
out = append(out, map[string]any{
|
||||
"id": item["id"],
|
||||
"product_id": item["product_id"],
|
||||
"name": firstNonEmpty(asMapString(item["processed_name"]), asMapString(item["name"])),
|
||||
"quality_score": q.Score,
|
||||
"quality_grade": q.Grade,
|
||||
"quality_checks": q.Checks,
|
||||
})
|
||||
}
|
||||
JSON(w, http.StatusOK, map[string]any{
|
||||
"products": out,
|
||||
"total": total,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
}
|
||||
|
||||
func asMapString(v any) string {
|
||||
if s, ok := v.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func attachProductQuality(items []map[string]any) {
|
||||
for i := range items {
|
||||
q := marketing.ScoreFromProductMap(items[i])
|
||||
items[i]["quality_score"] = q.Score
|
||||
items[i]["quality_grade"] = q.Grade
|
||||
items[i]["quality_checks"] = q.Checks
|
||||
// Drop heavy fields used only for scoring when present on list payloads.
|
||||
delete(items[i], "mapped_data")
|
||||
delete(items[i], "attributes")
|
||||
delete(items[i], "processed_attributes")
|
||||
delete(items[i], "description")
|
||||
delete(items[i], "processed_description")
|
||||
delete(items[i], "meta_title")
|
||||
delete(items[i], "meta_description")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user