kafka/internal/engines/crossref.go
Franz Kafka 6346fb7155 chore: update Go module path to github.com/metamorphosis-dev/kafka
Module path now matches the GitHub mirror location.
All internal imports updated across 35+ files.
2026-03-21 19:42:01 +00:00

144 lines
3.4 KiB
Go

package engines
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/metamorphosis-dev/kafka/internal/contracts"
)
type CrossrefEngine struct {
client *http.Client
}
func (e *CrossrefEngine) Name() string { return "crossref" }
func (e *CrossrefEngine) Search(ctx context.Context, req contracts.SearchRequest) (contracts.SearchResponse, error) {
if e == nil || e.client == nil {
return contracts.SearchResponse{}, errors.New("crossref engine not initialized")
}
q := strings.TrimSpace(req.Query)
if q == "" {
return contracts.SearchResponse{Query: req.Query}, nil
}
offset := 20 * (req.Pageno - 1)
args := url.Values{}
args.Set("query", q)
args.Set("offset", fmt.Sprintf("%d", offset))
endpoint := "https://api.crossref.org/works?" + args.Encode()
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return contracts.SearchResponse{}, err
}
resp, err := e.client.Do(httpReq)
if err != nil {
return contracts.SearchResponse{}, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 16*1024))
return contracts.SearchResponse{}, fmt.Errorf("crossref upstream error: status=%d body=%q", resp.StatusCode, string(body))
}
var api struct {
Message struct {
Items []crossrefItem `json:"items"`
} `json:"message"`
}
if err := json.NewDecoder(resp.Body).Decode(&api); err != nil {
return contracts.SearchResponse{}, err
}
results := make([]contracts.MainResult, 0, len(api.Message.Items))
for _, item := range api.Message.Items {
title := ""
if len(item.Title) > 0 {
title = strings.TrimSpace(item.Title[0])
}
content := strings.TrimSpace(item.Abstract)
urlStr := strings.TrimSpace(item.URL)
if urlStr == "" {
urlStr = strings.TrimSpace(item.DOI)
}
pub := parseCrossrefDateParts(item.Published.DateParts)
urlPtr := urlStr
results = append(results, contracts.MainResult{
Template: "default.html",
Title: title,
Content: content,
URL: &urlPtr,
Pubdate: pub,
Engine: "crossref",
Score: 0,
Category: "science",
Priority: "",
Positions: nil,
Engines: []string{"crossref"},
})
}
return contracts.SearchResponse{
Query: req.Query,
NumberOfResults: len(results),
Results: results,
Answers: []map[string]any{},
Corrections: []string{},
Infoboxes: []map[string]any{},
Suggestions: []string{},
UnresponsiveEngines: [][2]string{},
}, nil
}
type crossrefItem struct {
Type string `json:"type"`
Title []string `json:"title"`
URL string `json:"URL"`
DOI string `json:"DOI"`
Abstract string `json:"abstract"`
Page string `json:"page"`
Publisher string `json:"publisher"`
Subject []string `json:"subject"`
Published crossrefPublished `json:"published"`
}
type crossrefPublished struct {
DateParts [][]int `json:"date-parts"`
}
func parseCrossrefDateParts(parts [][]int) *string {
if len(parts) == 0 || len(parts[0]) == 0 {
return nil
}
dp := parts[0]
year := dp[0]
month := 1
day := 1
if len(dp) >= 2 {
month = dp[1]
}
if len(dp) >= 3 {
day = dp[2]
}
t := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
formatted := t.Format("2006-01-02 00:00:00+0000")
return &formatted
}