786 lines
21 KiB
Go
786 lines
21 KiB
Go
package http
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"path"
|
|
|
|
"github.com/influxdata/httprouter"
|
|
"github.com/influxdata/influxdb"
|
|
pcontext "github.com/influxdata/influxdb/context"
|
|
"github.com/influxdata/influxdb/kit/tracing"
|
|
"github.com/influxdata/influxdb/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 {
|
|
CreateDocument(ctx context.Context, namespace string, orgID influxdb.ID, d *influxdb.Document) error
|
|
GetDocuments(ctx context.Context, namespace string, orgID influxdb.ID) ([]*influxdb.Document, error)
|
|
GetDocument(ctx context.Context, namespace string, id influxdb.ID) (*influxdb.Document, error)
|
|
UpdateDocument(ctx context.Context, namespace string, d *influxdb.Document) error
|
|
DeleteDocument(ctx context.Context, namespace string, id influxdb.ID) error
|
|
|
|
GetDocumentLabels(ctx context.Context, namespace string, id influxdb.ID) ([]*influxdb.Label, error)
|
|
AddDocumentLabel(ctx context.Context, namespace string, did influxdb.ID, lid influxdb.ID) (*influxdb.Label, error)
|
|
DeleteDocumentLabel(ctx context.Context, namespace string, did influxdb.ID, lid influxdb.ID) error
|
|
}
|
|
|
|
// DocumentBackend is all services and associated parameters required to construct
|
|
// the DocumentHandler.
|
|
type DocumentBackend struct {
|
|
log *zap.Logger
|
|
influxdb.HTTPErrorHandler
|
|
|
|
DocumentService influxdb.DocumentService
|
|
LabelService influxdb.LabelService
|
|
}
|
|
|
|
// 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,
|
|
LabelService: b.LabelService,
|
|
}
|
|
}
|
|
|
|
// DocumentHandler represents an HTTP API handler for documents.
|
|
type DocumentHandler struct {
|
|
*httprouter.Router
|
|
|
|
log *zap.Logger
|
|
influxdb.HTTPErrorHandler
|
|
|
|
DocumentService influxdb.DocumentService
|
|
LabelService influxdb.LabelService
|
|
}
|
|
|
|
const (
|
|
documentsPath = "/api/v2/documents/:ns"
|
|
documentPath = "/api/v2/documents/:ns/:id"
|
|
documentLabelsPath = "/api/v2/documents/:ns/:id/labels"
|
|
documentLabelsIDPath = "/api/v2/documents/:ns/:id/labels/:lid"
|
|
)
|
|
|
|
// 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,
|
|
LabelService: b.LabelService,
|
|
}
|
|
|
|
h.HandlerFunc("POST", documentsPath, h.handlePostDocument)
|
|
h.HandlerFunc("GET", documentsPath, h.handleGetDocuments)
|
|
h.HandlerFunc("GET", documentPath, h.handleGetDocument)
|
|
h.HandlerFunc("PUT", documentPath, h.handlePutDocument)
|
|
h.HandlerFunc("DELETE", documentPath, h.handleDeleteDocument)
|
|
|
|
h.HandlerFunc("GET", documentLabelsPath, h.handleGetDocumentLabel)
|
|
h.HandlerFunc("POST", documentLabelsPath, h.handlePostDocumentLabel)
|
|
h.HandlerFunc("DELETE", documentLabelsIDPath, h.handleDeleteDocumentLabel)
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
// handlePostDocument is the HTTP handler for the POST /api/v2/documents/:ns route.
|
|
func (h *DocumentHandler) handlePostDocument(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
req, err := decodePostDocumentRequest(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
|
|
}
|
|
|
|
a, err := pcontext.GetAuthorizer(ctx)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
opts := []influxdb.DocumentOptions{}
|
|
if req.OrgID.Valid() {
|
|
opts = append(opts, influxdb.AuthorizedWithOrgID(a, req.OrgID))
|
|
} else {
|
|
opts = append(opts, influxdb.AuthorizedWithOrg(a, req.Org))
|
|
}
|
|
for _, label := range req.Labels {
|
|
// TODO(desa): make these AuthorizedWithLabel eventually
|
|
opts = append(opts, influxdb.WithLabel(label))
|
|
}
|
|
|
|
if err := s.CreateDocument(ctx, req.Document, opts...); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
h.log.Debug("Document created", zap.String("document", fmt.Sprint(req.Document)))
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusCreated, newDocumentResponse(req.Namespace, req.Document)); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
type postDocumentRequest struct {
|
|
*influxdb.Document
|
|
Namespace string `json:"-"`
|
|
Org string `json:"org"`
|
|
OrgID influxdb.ID `json:"orgID,omitempty"`
|
|
Labels []influxdb.ID `json:"labels"`
|
|
}
|
|
|
|
func decodePostDocumentRequest(ctx context.Context, r *http.Request) (*postDocumentRequest, error) {
|
|
req := &postDocumentRequest{}
|
|
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "document body error",
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
if req.Document == nil {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "missing document body",
|
|
}
|
|
}
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
req.Namespace = params.ByName("ns")
|
|
if req.Namespace == "" {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "url missing namespace",
|
|
}
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
a, err := pcontext.GetAuthorizer(ctx)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
opts := []influxdb.DocumentFindOptions{influxdb.IncludeLabels}
|
|
if req.Org != "" && req.OrgID != nil {
|
|
h.HandleHTTPError(ctx, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "Please provide either org or orgID, not both",
|
|
}, w)
|
|
return
|
|
} else if req.OrgID != nil && req.OrgID.Valid() {
|
|
opt := influxdb.AuthorizedWhereOrgID(a, *req.OrgID)
|
|
opts = append(opts, opt)
|
|
} else if req.Org != "" {
|
|
opt := influxdb.AuthorizedWhereOrg(a, req.Org)
|
|
opts = append(opts, opt)
|
|
}
|
|
|
|
ds, err := s.FindDocuments(ctx, opts...)
|
|
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 *influxdb.ID
|
|
}
|
|
|
|
func decodeGetDocumentsRequest(ctx context.Context, r *http.Request) (*getDocumentsRequest, error) {
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
ns := params.ByName("ns")
|
|
if ns == "" {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "url missing namespace",
|
|
}
|
|
}
|
|
|
|
qp := r.URL.Query()
|
|
var oid *influxdb.ID
|
|
var err error
|
|
|
|
if oidStr := qp.Get("orgID"); oidStr != "" {
|
|
oid, err = influxdb.IDFromString(oidStr)
|
|
if err != nil {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "Invalid orgID",
|
|
}
|
|
}
|
|
}
|
|
return &getDocumentsRequest{
|
|
Namespace: ns,
|
|
Org: qp.Get("org"),
|
|
OrgID: oid,
|
|
}, nil
|
|
}
|
|
|
|
func (h *DocumentHandler) handlePostDocumentLabel(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
_, _, err := h.getDocument(w, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
req, err := decodePostLabelMappingRequest(ctx, r, influxdb.DocumentsResourceType)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := req.Mapping.Validate(); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := h.LabelService.CreateLabelMapping(ctx, &req.Mapping); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
label, err := h.LabelService.FindLabelByID(ctx, req.Mapping.LabelID)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
h.log.Debug("Document label created", zap.String("label", fmt.Sprint(label)))
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusCreated, newLabelResponse(label)); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// handleDeleteDocumentLabel will first remove the label from the document,
|
|
// then remove that label.
|
|
func (h *DocumentHandler) handleDeleteDocumentLabel(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
req, err := decodeDeleteLabelMappingRequest(ctx, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
_, _, err = h.getDocument(w, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
_, err = h.LabelService.FindLabelByID(ctx, req.LabelID)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
mapping := &influxdb.LabelMapping{
|
|
LabelID: req.LabelID,
|
|
ResourceID: req.ResourceID,
|
|
ResourceType: influxdb.DocumentsResourceType,
|
|
}
|
|
|
|
// remove the label
|
|
if err := h.LabelService.DeleteLabelMapping(ctx, mapping); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
h.log.Debug("Document label deleted", zap.String("mapping", fmt.Sprint(mapping)))
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *DocumentHandler) handleGetDocumentLabel(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
d, _, err := h.getDocument(w, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
h.log.Debug("Document label retrieved", zap.String("labels", fmt.Sprint(d.Labels)))
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newLabelsResponse(d.Labels)); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (h *DocumentHandler) getDocument(w http.ResponseWriter, r *http.Request) (*influxdb.Document, string, error) {
|
|
ctx := r.Context()
|
|
|
|
req, err := decodeGetDocumentRequest(ctx, r)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
s, err := h.DocumentService.FindDocumentStore(ctx, req.Namespace)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
a, err := pcontext.GetAuthorizer(ctx)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
ds, err := s.FindDocuments(ctx, influxdb.AuthorizedWhereID(a, req.ID), influxdb.IncludeContent, influxdb.IncludeLabels)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
if len(ds) != 1 {
|
|
return nil, "", &influxdb.Error{
|
|
Code: influxdb.EInternal,
|
|
Msg: fmt.Sprintf("found more than one document with id %s; please report this error", req.ID),
|
|
}
|
|
}
|
|
return ds[0], req.Namespace, nil
|
|
}
|
|
|
|
// handleGetDocument is the HTTP handler for the GET /api/v2/documents/:ns/:id route.
|
|
func (h *DocumentHandler) handleGetDocument(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
d, namespace, err := h.getDocument(w, r)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
h.log.Debug("Document retrieved", zap.String("document", fmt.Sprint(d)))
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newDocumentResponse(namespace, d)); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
type getDocumentRequest struct {
|
|
Namespace string
|
|
ID influxdb.ID
|
|
}
|
|
|
|
func decodeGetDocumentRequest(ctx context.Context, r *http.Request) (*getDocumentRequest, error) {
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
ns := params.ByName("ns")
|
|
if ns == "" {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "url missing namespace",
|
|
}
|
|
}
|
|
|
|
i := params.ByName("id")
|
|
if i == "" {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "url missing id",
|
|
}
|
|
}
|
|
|
|
var id influxdb.ID
|
|
if err := id.DecodeFromString(i); err != nil {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "bad id in url",
|
|
}
|
|
}
|
|
|
|
return &getDocumentRequest{
|
|
Namespace: ns,
|
|
ID: id,
|
|
}, nil
|
|
}
|
|
|
|
// handleDeleteDocument is the HTTP handler for the DELETE /api/v2/documents/:ns/:id route.
|
|
func (h *DocumentHandler) handleDeleteDocument(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
req, err := decodeDeleteDocumentRequest(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
|
|
}
|
|
|
|
a, err := pcontext.GetAuthorizer(ctx)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := s.DeleteDocuments(ctx, influxdb.AuthorizedWhereID(a, req.ID)); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
h.log.Debug("Document deleted", zap.String("documentID", fmt.Sprint(req.ID)))
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
type deleteDocumentRequest struct {
|
|
Namespace string
|
|
ID influxdb.ID
|
|
}
|
|
|
|
func decodeDeleteDocumentRequest(ctx context.Context, r *http.Request) (*deleteDocumentRequest, error) {
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
ns := params.ByName("ns")
|
|
if ns == "" {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "url missing namespace",
|
|
}
|
|
}
|
|
|
|
i := params.ByName("id")
|
|
if i == "" {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "url missing id",
|
|
}
|
|
}
|
|
|
|
var id influxdb.ID
|
|
if err := id.DecodeFromString(i); err != nil {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "bad id in url",
|
|
}
|
|
}
|
|
|
|
return &deleteDocumentRequest{
|
|
Namespace: ns,
|
|
ID: id,
|
|
}, nil
|
|
}
|
|
|
|
// handlePutDocument is the HTTP handler for the PUT /api/v2/documents/:ns/:id route.
|
|
func (h *DocumentHandler) handlePutDocument(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
req, err := decodePutDocumentRequest(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
|
|
}
|
|
|
|
a, err := pcontext.GetAuthorizer(ctx)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if err := s.UpdateDocument(ctx, req.Document, influxdb.Authorized(a)); err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
ds, err := s.FindDocuments(ctx, influxdb.WhereID(req.Document.ID), influxdb.IncludeContent, influxdb.IncludeLabels)
|
|
if err != nil {
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
if len(ds) != 1 {
|
|
err := &influxdb.Error{
|
|
Code: influxdb.EInternal,
|
|
Msg: fmt.Sprintf("found more than one document with id %s; please report this error", req.ID),
|
|
}
|
|
h.HandleHTTPError(ctx, err, w)
|
|
return
|
|
}
|
|
|
|
d := ds[0]
|
|
|
|
h.log.Debug("Document updated", zap.String("document", fmt.Sprint(d)))
|
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, newDocumentResponse(req.Namespace, d)); err != nil {
|
|
logEncodingError(h.log, r, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
type putDocumentRequest struct {
|
|
*influxdb.Document
|
|
Namespace string `json:"-"`
|
|
}
|
|
|
|
func decodePutDocumentRequest(ctx context.Context, r *http.Request) (*putDocumentRequest, error) {
|
|
req := &putDocumentRequest{}
|
|
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params := httprouter.ParamsFromContext(ctx)
|
|
i := params.ByName("id")
|
|
if i == "" {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "url missing id",
|
|
}
|
|
}
|
|
|
|
if err := req.ID.DecodeFromString(i); err != nil {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
req.Namespace = params.ByName("ns")
|
|
if req.Namespace == "" {
|
|
return nil, &influxdb.Error{
|
|
Code: influxdb.EInvalid,
|
|
Msg: "url missing namespace",
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
func buildDocumentPath(namespace string, id influxdb.ID) string {
|
|
return path.Join(prefixDocuments, namespace, id.String())
|
|
}
|
|
func buildDocumentLabelsPath(namespace string, id influxdb.ID) string {
|
|
return path.Join(prefixDocuments, namespace, id.String(), "labels")
|
|
}
|
|
func buildDocumentLabelPath(namespace string, did influxdb.ID, lid influxdb.ID) string {
|
|
return path.Join(prefixDocuments, namespace, did.String(), "labels", lid.String())
|
|
}
|
|
|
|
// CreateDocument creates a document in the specified namespace.
|
|
// Only the ids of the given labels will be used.
|
|
// After the call, if successful, the input document will contain the new one.
|
|
func (s *documentService) CreateDocument(ctx context.Context, namespace string, orgID influxdb.ID, d *influxdb.Document) error {
|
|
span, _ := tracing.StartSpanFromContext(ctx)
|
|
defer span.Finish()
|
|
|
|
lids := make([]influxdb.ID, len(d.Labels))
|
|
for i := 0; i < len(lids); i++ {
|
|
lids[i] = d.Labels[i].ID
|
|
}
|
|
// Set a valid ID for proper marshaling.
|
|
// It will be assigned by the backend in any case.
|
|
d.ID = influxdb.ID(1)
|
|
req := &postDocumentRequest{
|
|
Document: d,
|
|
OrgID: orgID,
|
|
Labels: lids,
|
|
}
|
|
var resp documentResponse
|
|
if err := s.Client.
|
|
PostJSON(req, buildDocumentsPath(namespace)).
|
|
DecodeJSON(&resp).
|
|
Do(ctx); err != nil {
|
|
return err
|
|
}
|
|
*d = *resp.Document
|
|
return nil
|
|
}
|
|
|
|
// 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 influxdb.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
|
|
}
|
|
|
|
// GetDocument returns the document with the specified id.
|
|
func (s *documentService) GetDocument(ctx context.Context, namespace string, id influxdb.ID) (*influxdb.Document, error) {
|
|
span, _ := tracing.StartSpanFromContext(ctx)
|
|
defer span.Finish()
|
|
|
|
var resp documentResponse
|
|
if err := s.Client.
|
|
Get(buildDocumentPath(namespace, id)).
|
|
DecodeJSON(&resp).
|
|
Do(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return resp.Document, nil
|
|
}
|
|
|
|
// UpdateDocument updates the document with id `d.ID` and replaces the content of `d` with the patched value.
|
|
func (s *documentService) UpdateDocument(ctx context.Context, namespace string, d *influxdb.Document) error {
|
|
span, _ := tracing.StartSpanFromContext(ctx)
|
|
defer span.Finish()
|
|
|
|
var resp documentResponse
|
|
if err := s.Client.
|
|
PutJSON(d, buildDocumentPath(namespace, d.ID)).
|
|
DecodeJSON(&resp).
|
|
Do(ctx); err != nil {
|
|
return err
|
|
}
|
|
*d = *resp.Document
|
|
return nil
|
|
}
|
|
|
|
// DeleteDocument deletes the document with the given id.
|
|
func (s *documentService) DeleteDocument(ctx context.Context, namespace string, id influxdb.ID) error {
|
|
span, _ := tracing.StartSpanFromContext(ctx)
|
|
defer span.Finish()
|
|
|
|
if err := s.Client.
|
|
Delete(buildDocumentPath(namespace, id)).
|
|
Do(ctx); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetDocumentLabels returns the labels associated to the document with the given id.
|
|
func (s *documentService) GetDocumentLabels(ctx context.Context, namespace string, id influxdb.ID) ([]*influxdb.Label, error) {
|
|
span, _ := tracing.StartSpanFromContext(ctx)
|
|
defer span.Finish()
|
|
|
|
var resp labelsResponse
|
|
if err := s.Client.
|
|
Get(buildDocumentLabelsPath(namespace, id)).
|
|
DecodeJSON(&resp).
|
|
Do(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return resp.Labels, nil
|
|
}
|
|
|
|
// AddDocumentLabel adds the label with id `lid` to the document with id `did`.
|
|
func (s *documentService) AddDocumentLabel(ctx context.Context, namespace string, did influxdb.ID, lid influxdb.ID) (*influxdb.Label, error) {
|
|
span, _ := tracing.StartSpanFromContext(ctx)
|
|
defer span.Finish()
|
|
|
|
mapping := &influxdb.LabelMapping{
|
|
LabelID: lid,
|
|
}
|
|
var resp labelResponse
|
|
if err := s.Client.
|
|
PostJSON(mapping, buildDocumentLabelsPath(namespace, did)).
|
|
DecodeJSON(&resp).
|
|
Do(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp.Label, nil
|
|
}
|
|
|
|
// DeleteDocumentLabel deletes the label with id `lid` from the document with id `did`.
|
|
func (s *documentService) DeleteDocumentLabel(ctx context.Context, namespace string, did influxdb.ID, lid influxdb.ID) error {
|
|
span, _ := tracing.StartSpanFromContext(ctx)
|
|
defer span.Finish()
|
|
|
|
if err := s.Client.
|
|
Delete(buildDocumentLabelPath(namespace, did, lid)).
|
|
Do(ctx); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|