snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
320
internal/relationtuple/transact_server.go
Normal file
320
internal/relationtuple/transact_server.go
Normal file
@@ -0,0 +1,320 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package relationtuple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/pkg/errors"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
|
||||
rts "github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2"
|
||||
"github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2/relationtuplesconnect"
|
||||
"github.com/ory/keto/internal/x/validate"
|
||||
"github.com/ory/keto/ketoapi"
|
||||
"github.com/ory/keto/x/events"
|
||||
)
|
||||
|
||||
var _ relationtuplesconnect.WriteServiceHandler = (*WriteHandler)(nil)
|
||||
|
||||
func protoTuplesWithAction(deltas []*rts.RelationTupleDelta, action rts.RelationTupleDelta_Action) (filtered []*ketoapi.RelationTuple, err error) {
|
||||
for _, d := range deltas {
|
||||
if d.Action == action {
|
||||
it, err := (&ketoapi.RelationTuple{}).FromDataProvider(d.RelationTuple)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filtered = append(filtered, it)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *WriteHandler) TransactRelationTuples(ctx context.Context, req *connect.Request[rts.TransactRelationTuplesRequest]) (*connect.Response[rts.TransactRelationTuplesResponse], error) {
|
||||
insertTuples, err := protoTuplesWithAction(req.Msg.RelationTupleDeltas, rts.RelationTupleDelta_ACTION_INSERT)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deleteTuples, err := protoTuplesWithAction(req.Msg.RelationTupleDeltas, rts.RelationTupleDelta_ACTION_DELETE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = h.d.RelationTupleManager().Transaction(ctx, func(ctx context.Context) error {
|
||||
its, err := h.d.Mapper().FromTuple(ctx, append(insertTuples, deleteTuples...)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.d.RelationTupleManager().TransactRelationTuples(ctx, its[:len(insertTuples)], its[len(insertTuples):])
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesChanged(ctx))
|
||||
|
||||
snaptokens := make([]string, len(insertTuples))
|
||||
for i := range insertTuples {
|
||||
snaptokens[i] = "not yet implemented"
|
||||
}
|
||||
return connect.NewResponse(&rts.TransactRelationTuplesResponse{
|
||||
Snaptokens: snaptokens,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (h *WriteHandler) DeleteRelationTuples(ctx context.Context, req *connect.Request[rts.DeleteRelationTuplesRequest]) (*connect.Response[rts.DeleteRelationTuplesResponse], error) {
|
||||
var q ketoapi.RelationQuery
|
||||
|
||||
switch {
|
||||
case req.Msg.RelationQuery != nil:
|
||||
q.FromDataProvider(&queryWrapper{req.Msg.RelationQuery})
|
||||
//lint:ignore SA1019 required for compatibility
|
||||
case req.Msg.Query != nil: //nolint:staticcheck
|
||||
//lint:ignore SA1019 backwards compatibility
|
||||
q.FromDataProvider(&deprecatedQueryWrapper{(*rts.ListRelationTuplesRequest_Query)(req.Msg.Query)}) //nolint:staticcheck
|
||||
default:
|
||||
return nil, errors.WithStack(herodot.ErrBadRequest().WithReason("invalid request"))
|
||||
}
|
||||
|
||||
iq, err := h.d.ReadOnlyMapper().FromQuery(ctx, &q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := h.d.RelationTupleManager().DeleteAllRelationTuples(ctx, iq); err != nil {
|
||||
return nil, errors.WithStack(herodot.ErrInternalServerError().WithError(err.Error()))
|
||||
}
|
||||
|
||||
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesDeleted(ctx))
|
||||
|
||||
return connect.NewResponse(&rts.DeleteRelationTuplesResponse{}), nil
|
||||
}
|
||||
|
||||
// Create Relationship Request Parameters
|
||||
//
|
||||
// swagger:parameters createRelationship
|
||||
//
|
||||
//lint:ignore U1000 required for OpenAPI
|
||||
type createRelationship struct {
|
||||
// in: body
|
||||
Body createRelationshipBody
|
||||
}
|
||||
|
||||
// Create Relationship Request Body
|
||||
//
|
||||
// swagger:model createRelationshipBody
|
||||
type createRelationshipBody struct {
|
||||
ketoapi.RelationQuery
|
||||
}
|
||||
|
||||
// swagger:route PUT /admin/relation-tuples relationship createRelationship
|
||||
//
|
||||
// # Create a Relationship
|
||||
//
|
||||
// Use this endpoint to create a relationship.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 201: relationship
|
||||
// 400: errorGeneric
|
||||
// default: errorGeneric
|
||||
//
|
||||
// Extensions:
|
||||
// x-ory-ratelimit-bucket: keto-admin-low
|
||||
func (h *WriteHandler) createRelation(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var rt ketoapi.RelationTuple
|
||||
if err := json.NewDecoder(r.Body).Decode(&rt); err != nil {
|
||||
h.d.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest().WithError(err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
if err := rt.Validate(); err != nil {
|
||||
h.d.Writer().WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.d.Logger().WithFields(rt.ToLoggerFields()).Debug("creating relation tuple")
|
||||
|
||||
err := h.d.RelationTupleManager().Transaction(ctx, func(ctx context.Context) error {
|
||||
it, err := h.d.Mapper().FromTuple(ctx, &rt)
|
||||
if err != nil {
|
||||
h.d.Logger().WithError(err).WithFields(rt.ToLoggerFields()).Errorf("could not map relation tuple to UUIDs")
|
||||
return err
|
||||
}
|
||||
if err := h.d.RelationTupleManager().WriteRelationTuples(ctx, it...); err != nil {
|
||||
h.d.Logger().WithError(err).WithFields(rt.ToLoggerFields()).Errorf("got an error while creating the relation tuple")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
h.d.Writer().WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesCreated(ctx))
|
||||
|
||||
h.d.Writer().WriteCreated(w, r,
|
||||
ReadRouteBase+"?"+rt.ToURLQuery().Encode(),
|
||||
&rt,
|
||||
)
|
||||
}
|
||||
|
||||
// swagger:route DELETE /admin/relation-tuples relationship deleteRelationships
|
||||
//
|
||||
// # Delete Relationships
|
||||
//
|
||||
// Use this endpoint to delete relationships
|
||||
//
|
||||
// Consumes:
|
||||
// - application/x-www-form-urlencoded
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 204: emptyResponse
|
||||
// 400: errorGeneric
|
||||
// default: errorGeneric
|
||||
//
|
||||
// Extensions:
|
||||
// x-ory-ratelimit-bucket: keto-admin-low
|
||||
func (h *WriteHandler) deleteRelations(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if err := validate.All(r,
|
||||
validate.NoExtraQueryParams(ketoapi.RelationQueryKeys...),
|
||||
validate.QueryParamsContainsOneOf(ketoapi.NamespaceKey),
|
||||
validate.HasEmptyBody(),
|
||||
); err != nil {
|
||||
h.d.Writer().WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
query, err := (&ketoapi.RelationQuery{}).FromURLQuery(q)
|
||||
if err != nil {
|
||||
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest().WithError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
l := h.d.Logger()
|
||||
for k := range q {
|
||||
l = l.WithField(k, q.Get(k))
|
||||
}
|
||||
l.Debug("deleting relationships")
|
||||
|
||||
iq, err := h.d.ReadOnlyMapper().FromQuery(ctx, query)
|
||||
if err != nil {
|
||||
h.d.Logger().WithError(err).Errorf("could not map fields to UUIDs")
|
||||
h.d.Writer().WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
if err := h.d.RelationTupleManager().DeleteAllRelationTuples(ctx, iq); err != nil {
|
||||
l.WithError(err).Errorf("got an error while deleting relationships")
|
||||
h.d.Writer().WriteError(w, r, herodot.ErrInternalServerError().WithError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesDeleted(ctx))
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func internalTuplesWithAction(deltas []*ketoapi.PatchDelta, action ketoapi.PatchAction) (filtered []*ketoapi.RelationTuple) {
|
||||
for _, d := range deltas {
|
||||
if d.Action == action {
|
||||
filtered = append(filtered, d.RelationTuple)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// swagger:route PATCH /admin/relation-tuples relationship patchRelationships
|
||||
//
|
||||
// # Patch Multiple Relationships
|
||||
//
|
||||
// Use this endpoint to patch one or more relationships.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 204: emptyResponse
|
||||
// 400: errorGeneric
|
||||
// 404: errorGeneric
|
||||
// default: errorGeneric
|
||||
//
|
||||
// Extensions:
|
||||
// x-ory-ratelimit-bucket: keto-admin-low
|
||||
func (h *WriteHandler) patchRelationTuples(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
var deltas []*ketoapi.PatchDelta
|
||||
if err := json.NewDecoder(r.Body).Decode(&deltas); err != nil {
|
||||
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest().WithError(err.Error()))
|
||||
return
|
||||
}
|
||||
for _, d := range deltas {
|
||||
if d.RelationTuple == nil {
|
||||
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest().WithError("relation_tuple is missing"))
|
||||
return
|
||||
}
|
||||
if err := d.RelationTuple.Validate(); err != nil {
|
||||
h.d.Writer().WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
switch d.Action {
|
||||
case ketoapi.ActionInsert, ketoapi.ActionDelete:
|
||||
default:
|
||||
h.d.Writer().WriteError(w, r, herodot.ErrBadRequest().WithError("unknown action "+string(d.Action)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
insertTuples := internalTuplesWithAction(deltas, ketoapi.ActionInsert)
|
||||
deleteTuples := internalTuplesWithAction(deltas, ketoapi.ActionDelete)
|
||||
|
||||
err := h.d.RelationTupleManager().Transaction(ctx, func(ctx context.Context) error {
|
||||
its, err := h.d.Mapper().FromTuple(ctx, append(insertTuples, deleteTuples...)...)
|
||||
if err != nil {
|
||||
h.d.Logger().WithError(err).Errorf("got an error while mapping fields to UUID")
|
||||
return err
|
||||
}
|
||||
return h.d.RelationTupleManager().TransactRelationTuples(ctx, its[:len(insertTuples)], its[len(insertTuples):])
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
h.d.Writer().WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
trace.SpanFromContext(ctx).AddEvent(events.NewRelationtuplesChanged(ctx))
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
Reference in New Issue
Block a user