feat: initial Phase 1 implementation — core framework + Docker
- Data types (SManga, SChapter, Page, MangasPage, all Filter variants) - Source interfaces (Source, CatalogueSource) with MD5-based ID generation matching Tachiyomi/Suwayomi - HTTP client with per-host rate limiting, cookie jar, and 429 retry - FlareSolverr v1 client (FLARESOLVERR_URL env) - Generic GraphQL POST helper - goquery HTML parser wrappers - Source registry (panics on duplicate ID) - Multi-stage Dockerfile (golang:1.26-alpine + distroless) and compose.yml (postgres, flaresolverr, app)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type GraphQLRequest struct {
|
||||
Query string `json:"query"`
|
||||
Variables any `json:"variables,omitempty"`
|
||||
}
|
||||
|
||||
type graphQLResponse[T any] struct {
|
||||
Data T `json:"data"`
|
||||
Errors []struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"errors"`
|
||||
}
|
||||
|
||||
// Post sends a GraphQL request and unmarshals the `data` field into T.
|
||||
func Post[T any](ctx context.Context, client *http.Client, url string, req GraphQLRequest, headers map[string]string) (T, error) {
|
||||
var zero T
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
for k, v := range headers {
|
||||
httpReq.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var gqlResp graphQLResponse[T]
|
||||
if err := json.NewDecoder(resp.Body).Decode(&gqlResp); err != nil {
|
||||
return zero, err
|
||||
}
|
||||
if len(gqlResp.Errors) > 0 {
|
||||
return zero, fmt.Errorf("graphql: %s", gqlResp.Errors[0].Message)
|
||||
}
|
||||
return gqlResp.Data, nil
|
||||
}
|
||||
Reference in New Issue
Block a user