247 lines
6.7 KiB
Go
247 lines
6.7 KiB
Go
package sharing
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type MemoryStore struct {
|
|
mu sync.RWMutex
|
|
policies map[string]PolicyRecord
|
|
tokens map[string]string
|
|
members map[string]map[string]MemberRecord
|
|
visits map[string]map[string]VisitRecord
|
|
}
|
|
|
|
func NewMemoryStore() *MemoryStore {
|
|
return &MemoryStore{
|
|
policies: make(map[string]PolicyRecord),
|
|
tokens: make(map[string]string),
|
|
members: make(map[string]map[string]MemberRecord),
|
|
visits: make(map[string]map[string]VisitRecord),
|
|
}
|
|
}
|
|
|
|
func (s *MemoryStore) GetPolicyByProject(ctx context.Context, projectID string) (PolicyRecord, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return PolicyRecord{}, err
|
|
}
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
policy, ok := s.policies[projectID]
|
|
if !ok {
|
|
return PolicyRecord{}, ErrNotFound
|
|
}
|
|
return clonePolicy(policy), nil
|
|
}
|
|
|
|
func (s *MemoryStore) GetPolicyByTokenHash(ctx context.Context, tokenHash []byte) (PolicyRecord, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return PolicyRecord{}, err
|
|
}
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
projectID, ok := s.tokens[string(tokenHash)]
|
|
if !ok {
|
|
return PolicyRecord{}, ErrNotFound
|
|
}
|
|
policy, ok := s.policies[projectID]
|
|
if !ok {
|
|
return PolicyRecord{}, ErrNotFound
|
|
}
|
|
return clonePolicy(policy), nil
|
|
}
|
|
|
|
func (s *MemoryStore) CreatePolicy(ctx context.Context, policy PolicyRecord) (PolicyRecord, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return PolicyRecord{}, err
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if existing, ok := s.policies[policy.ProjectID]; ok {
|
|
return clonePolicy(existing), nil
|
|
}
|
|
policy = clonePolicy(policy)
|
|
s.policies[policy.ProjectID] = policy
|
|
s.tokens[string(policy.TokenHash)] = policy.ProjectID
|
|
return clonePolicy(policy), nil
|
|
}
|
|
|
|
func (s *MemoryStore) UpdatePolicyPermission(ctx context.Context, projectID string, ownerID string, permission Permission, updatedAt time.Time) (PolicyRecord, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return PolicyRecord{}, err
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
policy, ok := s.policies[projectID]
|
|
if !ok {
|
|
return PolicyRecord{}, ErrNotFound
|
|
}
|
|
if policy.OwnerID != ownerID {
|
|
return PolicyRecord{}, ErrForbidden
|
|
}
|
|
policy.LinkPermission = permission
|
|
policy.Version++
|
|
policy.UpdatedAt = updatedAt
|
|
s.policies[projectID] = policy
|
|
return clonePolicy(policy), nil
|
|
}
|
|
|
|
func (s *MemoryStore) DeleteAll(ctx context.Context, projectID string, ownerID string) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
policy, ok := s.policies[projectID]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
if policy.OwnerID != ownerID {
|
|
return ErrForbidden
|
|
}
|
|
delete(s.tokens, string(policy.TokenHash))
|
|
delete(s.policies, projectID)
|
|
delete(s.members, projectID)
|
|
delete(s.visits, projectID)
|
|
return nil
|
|
}
|
|
|
|
func (s *MemoryStore) ListMembers(ctx context.Context, projectID string) ([]MemberRecord, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
items := make([]MemberRecord, 0, len(s.members[projectID]))
|
|
for _, member := range s.members[projectID] {
|
|
items = append(items, cloneMember(member))
|
|
}
|
|
sort.Slice(items, func(i, j int) bool { return items[i].CreatedAt.Before(items[j].CreatedAt) })
|
|
return items, nil
|
|
}
|
|
|
|
func (s *MemoryStore) CreateMember(ctx context.Context, member MemberRecord) (MemberRecord, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return MemberRecord{}, err
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if _, ok := s.policies[member.ProjectID]; !ok {
|
|
return MemberRecord{}, ErrNotFound
|
|
}
|
|
if s.members[member.ProjectID] == nil {
|
|
s.members[member.ProjectID] = make(map[string]MemberRecord)
|
|
}
|
|
for _, existing := range s.members[member.ProjectID] {
|
|
if existing.IdentifierType == member.IdentifierType && hashesEqual(existing.IdentifierHash, member.IdentifierHash) {
|
|
return MemberRecord{}, ErrMemberExists
|
|
}
|
|
}
|
|
member = cloneMember(member)
|
|
s.members[member.ProjectID][member.ID] = member
|
|
return cloneMember(member), nil
|
|
}
|
|
|
|
func (s *MemoryStore) UpdateMember(ctx context.Context, projectID string, memberID string, permission Permission, updatedAt time.Time) (MemberRecord, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return MemberRecord{}, err
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
member, ok := s.members[projectID][memberID]
|
|
if !ok {
|
|
return MemberRecord{}, ErrNotFound
|
|
}
|
|
member.Permission = permission
|
|
member.UpdatedAt = updatedAt
|
|
s.members[projectID][memberID] = member
|
|
return cloneMember(member), nil
|
|
}
|
|
|
|
func (s *MemoryStore) DeleteMember(ctx context.Context, projectID string, memberID string) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if _, ok := s.members[projectID][memberID]; !ok {
|
|
return ErrNotFound
|
|
}
|
|
delete(s.members[projectID], memberID)
|
|
return nil
|
|
}
|
|
|
|
func (s *MemoryStore) CountVisitors(ctx context.Context, projectID string) (int64, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return 0, err
|
|
}
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return int64(len(s.visits[projectID])), nil
|
|
}
|
|
|
|
func (s *MemoryStore) RecordVisit(ctx context.Context, visit VisitRecord) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if _, ok := s.policies[visit.ProjectID]; !ok {
|
|
return ErrNotFound
|
|
}
|
|
if s.visits[visit.ProjectID] == nil {
|
|
s.visits[visit.ProjectID] = make(map[string]VisitRecord)
|
|
}
|
|
key := string(visit.VisitorHash)
|
|
if current, ok := s.visits[visit.ProjectID][key]; ok {
|
|
current.VisitCount++
|
|
current.LastSeenAt = visit.LastSeenAt
|
|
if visit.UserID != "" {
|
|
current.UserID = visit.UserID
|
|
}
|
|
s.visits[visit.ProjectID][key] = current
|
|
return nil
|
|
}
|
|
s.visits[visit.ProjectID][key] = cloneVisit(visit)
|
|
return nil
|
|
}
|
|
|
|
func (s *MemoryStore) ListVisitors(ctx context.Context, projectID string, limit int64) ([]VisitRecord, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
items := make([]VisitRecord, 0, len(s.visits[projectID]))
|
|
for _, visit := range s.visits[projectID] {
|
|
items = append(items, cloneVisit(visit))
|
|
}
|
|
sort.Slice(items, func(i, j int) bool { return items[i].LastSeenAt.After(items[j].LastSeenAt) })
|
|
if limit > 0 && int64(len(items)) > limit {
|
|
items = items[:limit]
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *MemoryStore) Close() error { return nil }
|
|
|
|
func clonePolicy(policy PolicyRecord) PolicyRecord {
|
|
policy.TokenHash = append([]byte(nil), policy.TokenHash...)
|
|
policy.TokenCiphertext = append([]byte(nil), policy.TokenCiphertext...)
|
|
return policy
|
|
}
|
|
|
|
func cloneMember(member MemberRecord) MemberRecord {
|
|
member.IdentifierHash = append([]byte(nil), member.IdentifierHash...)
|
|
member.IdentifierCiphertext = append([]byte(nil), member.IdentifierCiphertext...)
|
|
return member
|
|
}
|
|
|
|
func cloneVisit(visit VisitRecord) VisitRecord {
|
|
visit.VisitorHash = append([]byte(nil), visit.VisitorHash...)
|
|
return visit
|
|
}
|