memos/store/memo_relation.go

139 lines
4.3 KiB
Go

package store
import (
"context"
"errors"
)
type MemoRelationType string
const (
// MemoRelationReference is the type for a reference memo relation.
MemoRelationReference MemoRelationType = "REFERENCE"
// MemoRelationComment is the type for a comment memo relation.
MemoRelationComment MemoRelationType = "COMMENT"
)
type MemoRelation struct {
MemoID int32
RelatedMemoID int32
Type MemoRelationType
}
// MemoRelationEndpointSnapshot is the memo-local database state used to
// authorize one endpoint during a reference-relation mutation.
type MemoRelationEndpointSnapshot struct {
ActorUserID int32
ActorActive bool
EndpointID int32
EndpointCreatorID int32
EndpointRowStatus RowStatus
EndpointVisibility Visibility
EndpointSpaceID *int32
EndpointSpaceExists bool
EndpointMemberActive bool
}
// ValidateMemoRelationEndpointRead applies authenticated memo-local read
// policy to an endpoint. Application roles are deliberately absent: an
// instance administrator has no relation-specific read bypass.
func ValidateMemoRelationEndpointRead(snapshot *MemoRelationEndpointSnapshot) error {
if snapshot == nil || !snapshot.ActorActive {
return ErrMemoPermissionDenied
}
if snapshot.EndpointID <= 0 {
return ErrMemoMutationConflict
}
if snapshot.EndpointRowStatus != Normal && snapshot.EndpointRowStatus != Archived {
return ErrMemoMutationConflict
}
if snapshot.EndpointRowStatus == Archived && snapshot.EndpointCreatorID != snapshot.ActorUserID {
return ErrMemoPermissionDenied
}
if !isValidVisibility(snapshot.EndpointVisibility) {
return ErrMemoMutationConflict
}
if snapshot.EndpointVisibility == SpaceAudience &&
(snapshot.EndpointSpaceID == nil || !snapshot.EndpointSpaceExists) {
return ErrMemoMutationConflict
}
switch snapshot.EndpointVisibility {
case Public, Protected:
return nil
case Private:
if snapshot.EndpointCreatorID != snapshot.ActorUserID {
return ErrMemoPermissionDenied
}
return nil
case SpaceAudience:
if !snapshot.EndpointMemberActive {
return ErrMemoPermissionDenied
}
return nil
default:
return ErrMemoMutationConflict
}
}
type FindMemoRelation struct {
MemoID *int32
RelatedMemoID *int32
Type *MemoRelationType
MemoFilter *string
// SourceMemoRowStatus filters the relation source memo before pagination.
SourceMemoRowStatus *RowStatus
// MemoIDList matches relations where memo_id OR related_memo_id is in the list.
MemoIDList []int32
// SourceMemoIDList matches relations where memo_id is in the list.
SourceMemoIDList []int32
// RelatedMemoIDList matches relations where related_memo_id is in the list.
RelatedMemoIDList []int32
Limit *int
Offset *int
}
type DeleteMemoRelation struct {
MemoID *int32
RelatedMemoID *int32
Type *MemoRelationType
}
// ValidateMemoRelationDelete ensures the generic deletion path can only
// mutate reference relations. COMMENT relations are immutable context and are
// removed only as part of the lifecycle of an incident memo.
func ValidateMemoRelationDelete(delete *DeleteMemoRelation) error {
if delete == nil {
return errors.New("memo relation deletion is required")
}
if delete.Type == nil || *delete.Type != MemoRelationReference {
return errors.New("only REFERENCE memo relations may be deleted")
}
if delete.MemoID == nil && delete.RelatedMemoID == nil {
return errors.New("memo relation deletion requires an endpoint")
}
if (delete.MemoID != nil && *delete.MemoID <= 0) || (delete.RelatedMemoID != nil && *delete.RelatedMemoID <= 0) {
return errors.New("memo relation deletion has an invalid endpoint")
}
return nil
}
func (s *Store) UpsertMemoRelation(ctx context.Context, create *MemoRelation) (*MemoRelation, error) {
if create == nil || create.Type != MemoRelationReference {
return nil, errors.New("only REFERENCE memo relations may be persisted")
}
return s.driver.UpsertMemoRelation(ctx, create)
}
func (s *Store) ListMemoRelations(ctx context.Context, find *FindMemoRelation) ([]*MemoRelation, error) {
return s.driver.ListMemoRelations(ctx, find)
}
func (s *Store) DeleteMemoRelation(ctx context.Context, delete *DeleteMemoRelation) error {
if err := ValidateMemoRelationDelete(delete); err != nil {
return err
}
return s.driver.DeleteMemoRelation(ctx, delete)
}