205 lines
5.0 KiB
Go
205 lines
5.0 KiB
Go
package http
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"path"
|
|
|
|
"github.com/influxdata/httprouter"
|
|
"github.com/influxdata/influxdb/v2"
|
|
"github.com/influxdata/influxdb/v2/kit/platform"
|
|
"github.com/influxdata/influxdb/v2/kit/platform/errors"
|
|
"github.com/influxdata/influxdb/v2/kit/tracing"
|
|
"github.com/influxdata/influxdb/v2/pkg/httpc"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const prefixDocuments = "/api/v2/documents"
|
|
|
|
// DocumentService is an interface HTTP-exposed portion of the document service.
|
|
type DocumentService interface {
|
|
GetDocuments(ctx context.Context, namespace string, orgID platform.ID) ([]*influxdb.Document, error)
|
|
}
|
|
|
|
// DocumentBackend is all services and associated parameters required to construct
|
|
// the DocumentHandler.
|
|
type DocumentBackend struct {
|
|
log *zap.Logger
|
|
errors.HTTPErrorHandler
|
|
|
|
DocumentService influxdb.DocumentService
|
|
}
|
|
|
|
// NewDocumentBackend returns a new instance of DocumentBackend.
|
|
func NewDocumentBackend(log *zap.Logger, b *APIBackend) *DocumentBackend {
|
|
return &DocumentBackend{
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
|
log: log,
|
|
DocumentService: b.DocumentService,
|
|
}
|
|
}
|
|
|
|
// DocumentHandler represents an HTTP API handler for documents.
|
|
type DocumentHandler struct {
|
|
*httprouter.Router
|
|
|
|
log *zap.Logger
|
|
errors.HTTPErrorHandler
|
|
|
|
DocumentService influxdb.DocumentService
|
|
LabelService influxdb.LabelService
|
|
}
|
|
|
|
const (
|
|
documentsPath = "/api/v2/documents/:ns"
|
|
)
|
|
|
|
// NewDocumentHandler returns a new instance of DocumentHandler.
|
|
// TODO(desa): this should probably take a namespace
|
|
func NewDocumentHandler(b *DocumentBackend) *DocumentHandler {
|
|
h := &DocumentHandler{
|
|
Router: NewRouter(b.HTTPErrorHandler),
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
|
log: b.log,
|
|
|
|
DocumentService: b.DocumentService,
|
|
}
|
|
|
|
h.HandlerFunc("GET", documentsPath, h.handleGetDocuments)
|
|
|
|
return h
|
|
}
|
|
|
|
type documentResponse struct {
|
|
Links map[string]string `json:"links"`
|
|
*influxdb.Document
|
|
}
|
|
|
|
func newDocumentResponse(ns string, d *influxdb.Document) *documentResponse {
|
|
if d.Labels == nil {
|
|
d.Labels = []*influxdb.Label{}
|
|
}
|
|
return &documentResponse{
|
|
Links: map[string]string{
|
|
"self": fmt.Sprintf("/api/v2/documents/%s/%s", ns, d.ID),
|
|
},
|
|
Document: d,
|
|
}
|
|
}
|
|
|
|
type documentsResponse struct {
|
|
Documents []*documentResponse `json:"documents"`
|
|
}
|
|
|
|
func newDocumentsResponse(ns string, docs []*influxdb.Document) *documentsResponse {
|
|
ds := make([]*documentResponse, 0, len(docs))
|
|
for _, doc := range docs {
|
|
ds = append(ds, newDocumentResponse(ns, doc))
|
|
}
|
|
|
|
return &documentsResponse{
|
|
Documents: ds,
|
|
}
|
|
}
|
|
|
|
// handleGetDocuments is the HTTP handler for the GET /api/v2/documents/:ns route.
|
|
func (h *DocumentHandler) handleGetDocuments(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
req, err := decodeGetDocumentsRequest(ctx, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
s, err := h.DocumentService.FindDocumentStore(ctx, req.Namespace)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
ds, err := s.FindDocuments(ctx, req.OrgID)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
h.log.Debug("Documents retrieved", zap.String("documents", fmt.Sprint(ds)))
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newDocumentsResponse(req.Namespace, ds)); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
type getDocumentsRequest struct {
|
|
Namespace string
|
|
Org string
|
|
OrgID platform.ID
|
|
}
|
|
|
|
func decodeGetDocumentsRequest(ctx context.Context, r *http.Request) (*getDocumentsRequest, error) {
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
ns := params.ByName("ns")
|
|
if ns == "" {
|
|
return nil, &errors.Error{
|
|
Code: errors.EInvalid,
|
|
Msg: "url missing namespace",
|
|
}
|
|
}
|
|
|
|
qp := r.URL.Query()
|
|
req := &getDocumentsRequest{
|
|
Namespace: ns,
|
|
Org: qp.Get("org"),
|
|
}
|
|
|
|
if oidStr := qp.Get("orgID"); oidStr != "" {
|
|
oid, err := platform.IDFromString(oidStr)
|
|
if err != nil {
|
|
return nil, &errors.Error{
|
|
Code: errors.EInvalid,
|
|
Msg: "Invalid orgID",
|
|
}
|
|
}
|
|
req.OrgID = *oid
|
|
}
|
|
return req, nil
|
|
}
|
|
|
|
type documentService struct {
|
|
Client *httpc.Client
|
|
}
|
|
|
|
// NewDocumentService creates a client to connect to Influx via HTTP to manage documents.
|
|
func NewDocumentService(client *httpc.Client) DocumentService {
|
|
return &documentService{
|
|
Client: client,
|
|
}
|
|
}
|
|
|
|
func buildDocumentsPath(namespace string) string {
|
|
return path.Join(prefixDocuments, namespace)
|
|
}
|
|
|
|
// GetDocuments returns the documents for a `namespace` and an `orgID`.
|
|
// Returned documents do not contain their content.
|
|
func (s *documentService) GetDocuments(ctx context.Context, namespace string, orgID platform.ID) ([]*influxdb.Document, error) {
|
|
span, _ := tracing.StartSpanFromContext(ctx)
|
|
defer span.Finish()
|
|
|
|
var resp documentsResponse
|
|
r := s.Client.
|
|
Get(buildDocumentsPath(namespace)).
|
|
DecodeJSON(&resp)
|
|
r = r.QueryParams([2]string{"orgID", orgID.String()})
|
|
if err := r.Do(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
docs := make([]*influxdb.Document, len(resp.Documents))
|
|
for i := 0; i < len(docs); i++ {
|
|
docs[i] = resp.Documents[i].Document
|
|
}
|
|
return docs, nil
|
|
}
|