144 lines
3 KiB
Go
144 lines
3 KiB
Go
|
package shares
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"time"
|
||
|
|
||
|
"dynatron.me/x/stillbox/internal/common"
|
||
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||
|
"dynatron.me/x/stillbox/pkg/calls/callstore"
|
||
|
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||
|
"dynatron.me/x/stillbox/pkg/users"
|
||
|
|
||
|
"github.com/google/uuid"
|
||
|
"github.com/matoous/go-nanoid"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
SlugLength = 20
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ErrExpirationTooSoon = errors.New("expiration too soon")
|
||
|
ErrBadType = errors.New("bad share type")
|
||
|
)
|
||
|
|
||
|
type EntityType string
|
||
|
|
||
|
const (
|
||
|
EntityIncident EntityType = "incident"
|
||
|
EntityCall EntityType = "call"
|
||
|
)
|
||
|
|
||
|
func (et EntityType) IsValid() bool {
|
||
|
switch et {
|
||
|
case EntityCall, EntityIncident:
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// If an incident is shared, all calls that are part of it must be shared too, but this can be through the incident share (/share/bLaH/callID[.mp3])
|
||
|
|
||
|
type Share struct {
|
||
|
ID string `json:"id"`
|
||
|
Type EntityType `json:"entityType"`
|
||
|
Date *jsontypes.Time `json:"-"` // we handle this for the user
|
||
|
Owner users.UserID `json:"owner"`
|
||
|
EntityID uuid.UUID `json:"entityID"`
|
||
|
Expiration *jsontypes.Time `json:"expiration"`
|
||
|
}
|
||
|
|
||
|
func (s *Share) GetName() string {
|
||
|
return "SHARE:" + s.ID
|
||
|
}
|
||
|
|
||
|
func (s *Share) GetRoles() []string {
|
||
|
return []string{rbac.RoleShareGuest}
|
||
|
}
|
||
|
|
||
|
func (s *Share) GetResourceName() string {
|
||
|
return rbac.ResourceShare
|
||
|
}
|
||
|
|
||
|
type CreateShareParams struct {
|
||
|
Type EntityType `json:"entityType"`
|
||
|
EntityID uuid.UUID `json:"entityID"`
|
||
|
Expiration *jsontypes.Duration `json:"expiration"`
|
||
|
}
|
||
|
|
||
|
func (s *service) checkEntity(ctx context.Context, sh *CreateShareParams) (*time.Time, error) {
|
||
|
var t *time.Time
|
||
|
switch sh.Type {
|
||
|
case EntityCall:
|
||
|
cs := callstore.FromCtx(ctx)
|
||
|
// Call does RBAC for us
|
||
|
call, err := cs.Call(ctx, sh.EntityID)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
t = &call.DateTime
|
||
|
case EntityIncident:
|
||
|
is := incstore.FromCtx(ctx)
|
||
|
i, err := is.Owner(ctx, sh.EntityID)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
_, err = rbac.Check(ctx, &i, rbac.WithActions(rbac.ActionShare))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return t, nil
|
||
|
}
|
||
|
|
||
|
// NewShare creates a new share.
|
||
|
func (s *service) NewShare(ctx context.Context, sh CreateShareParams) (*Share, error) {
|
||
|
if !sh.Type.IsValid() {
|
||
|
return nil, ErrBadType
|
||
|
}
|
||
|
|
||
|
// entTime is only meaningful if we are a call
|
||
|
entTime, err := s.checkEntity(ctx, &sh)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
u, err := users.From(ctx)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
id, err := gonanoid.ID(SlugLength)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
share := &Share{
|
||
|
ID: id,
|
||
|
Type: sh.Type,
|
||
|
Date: (*jsontypes.Time)(entTime),
|
||
|
Owner: u.ID,
|
||
|
EntityID: sh.EntityID,
|
||
|
}
|
||
|
|
||
|
if sh.Expiration != nil {
|
||
|
if sh.Expiration.Duration() < time.Minute {
|
||
|
return nil, ErrExpirationTooSoon
|
||
|
}
|
||
|
share.Expiration = common.PtrTo(jsontypes.Time(time.Now().Add(sh.Expiration.Duration())))
|
||
|
}
|
||
|
|
||
|
err = s.Create(ctx, share)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return share, nil
|
||
|
}
|