139 lines
4.3 KiB
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)
|
|
}
|