accounts/internal/store/xworkmate_memory.go
2026-03-17 13:25:21 +08:00

324 lines
7.8 KiB
Go

package store
import (
"context"
"sort"
"strings"
"time"
"github.com/google/uuid"
)
func tenantProfileKey(tenantID, userID, scope string) string {
return strings.Join([]string{
strings.TrimSpace(tenantID),
strings.TrimSpace(userID),
NormalizeXWorkmateProfileScope(scope),
}, "|")
}
func (s *memoryStore) EnsureTenant(ctx context.Context, tenant *Tenant) error {
_ = ctx
if tenant == nil {
return ErrTenantNotFound
}
NormalizeTenant(tenant)
if tenant.ID == "" {
tenant.ID = uuid.NewString()
}
now := time.Now().UTC()
s.mu.Lock()
defer s.mu.Unlock()
existing, ok := s.tenants[tenant.ID]
if ok {
existing.Name = tenant.Name
existing.Edition = tenant.Edition
existing.UpdatedAt = now
tenant.CreatedAt = existing.CreatedAt
tenant.UpdatedAt = existing.UpdatedAt
return nil
}
stored := &Tenant{
ID: tenant.ID,
Name: tenant.Name,
Edition: tenant.Edition,
CreatedAt: now,
UpdatedAt: now,
}
s.tenants[stored.ID] = stored
tenant.CreatedAt = stored.CreatedAt
tenant.UpdatedAt = stored.UpdatedAt
return nil
}
func (s *memoryStore) EnsureTenantDomain(ctx context.Context, domain *TenantDomain) error {
_ = ctx
if domain == nil {
return ErrTenantNotFound
}
NormalizeTenantDomain(domain)
if domain.Domain == "" || domain.TenantID == "" {
return ErrTenantNotFound
}
if domain.ID == "" {
domain.ID = uuid.NewString()
}
now := time.Now().UTC()
s.mu.Lock()
defer s.mu.Unlock()
existing, ok := s.tenantDomains[domain.Domain]
if ok {
existing.TenantID = domain.TenantID
existing.Kind = domain.Kind
existing.IsPrimary = domain.IsPrimary
existing.Status = domain.Status
existing.UpdatedAt = now
domain.CreatedAt = existing.CreatedAt
domain.UpdatedAt = existing.UpdatedAt
return nil
}
stored := &TenantDomain{
ID: domain.ID,
TenantID: domain.TenantID,
Domain: domain.Domain,
Kind: domain.Kind,
IsPrimary: domain.IsPrimary,
Status: domain.Status,
CreatedAt: now,
UpdatedAt: now,
}
s.tenantDomains[stored.Domain] = stored
domain.CreatedAt = stored.CreatedAt
domain.UpdatedAt = stored.UpdatedAt
return nil
}
func (s *memoryStore) UpsertTenantMembership(ctx context.Context, membership *TenantMembership) error {
_ = ctx
if membership == nil {
return ErrTenantMembershipNotFound
}
NormalizeTenantMembership(membership)
if membership.TenantID == "" || membership.UserID == "" {
return ErrTenantMembershipNotFound
}
now := time.Now().UTC()
s.mu.Lock()
defer s.mu.Unlock()
if s.tenantMemberships[membership.TenantID] == nil {
s.tenantMemberships[membership.TenantID] = make(map[string]*TenantMembership)
}
if existing, ok := s.tenantMemberships[membership.TenantID][membership.UserID]; ok {
existing.Role = membership.Role
existing.UpdatedAt = now
membership.CreatedAt = existing.CreatedAt
membership.UpdatedAt = existing.UpdatedAt
return nil
}
stored := &TenantMembership{
TenantID: membership.TenantID,
UserID: membership.UserID,
Role: membership.Role,
CreatedAt: now,
UpdatedAt: now,
}
s.tenantMemberships[membership.TenantID][membership.UserID] = stored
membership.CreatedAt = stored.CreatedAt
membership.UpdatedAt = stored.UpdatedAt
return nil
}
func (s *memoryStore) ResolveTenantByHost(ctx context.Context, host string) (*Tenant, *TenantDomain, error) {
_ = ctx
normalizedHost := NormalizeHostname(host)
s.mu.RLock()
defer s.mu.RUnlock()
if IsSharedTenantHost(normalizedHost) {
tenant, ok := s.tenants[SharedXWorkmateTenantID]
if !ok {
return nil, nil, ErrTenantNotFound
}
var domain *TenantDomain
if storedDomain, ok := s.tenantDomains[SharedXWorkmateDomain]; ok {
domainCopy := *storedDomain
domain = &domainCopy
}
tenantCopy := *tenant
return &tenantCopy, domain, nil
}
domain, ok := s.tenantDomains[normalizedHost]
if !ok {
return nil, nil, ErrTenantNotFound
}
tenant, ok := s.tenants[domain.TenantID]
if !ok {
return nil, nil, ErrTenantNotFound
}
tenantCopy := *tenant
domainCopy := *domain
return &tenantCopy, &domainCopy, nil
}
func (s *memoryStore) ListTenantMembershipsByUser(ctx context.Context, userID string) ([]TenantMembership, error) {
_ = ctx
normalizedUserID := strings.TrimSpace(userID)
s.mu.RLock()
defer s.mu.RUnlock()
result := make([]TenantMembership, 0)
for tenantID, members := range s.tenantMemberships {
member, ok := members[normalizedUserID]
if !ok {
continue
}
entry := *member
if tenant, ok := s.tenants[tenantID]; ok {
entry.TenantName = tenant.Name
entry.TenantEdition = tenant.Edition
}
for _, domain := range s.tenantDomains {
if domain.TenantID == tenantID && domain.IsPrimary {
entry.Domain = domain.Domain
break
}
}
result = append(result, entry)
}
sort.Slice(result, func(i, j int) bool {
if result[i].TenantName == result[j].TenantName {
return result[i].TenantID < result[j].TenantID
}
return result[i].TenantName < result[j].TenantName
})
return result, nil
}
func (s *memoryStore) GetTenantMembership(ctx context.Context, tenantID, userID string) (*TenantMembership, error) {
_ = ctx
normalizedTenantID := strings.TrimSpace(tenantID)
normalizedUserID := strings.TrimSpace(userID)
s.mu.RLock()
defer s.mu.RUnlock()
members := s.tenantMemberships[normalizedTenantID]
if members == nil {
return nil, ErrTenantMembershipNotFound
}
member, ok := members[normalizedUserID]
if !ok {
return nil, ErrTenantMembershipNotFound
}
entry := *member
if tenant, ok := s.tenants[normalizedTenantID]; ok {
entry.TenantName = tenant.Name
entry.TenantEdition = tenant.Edition
}
for _, domain := range s.tenantDomains {
if domain.TenantID == normalizedTenantID && domain.IsPrimary {
entry.Domain = domain.Domain
break
}
}
return &entry, nil
}
func (s *memoryStore) GetXWorkmateProfile(ctx context.Context, tenantID, userID, scope string) (*XWorkmateProfile, error) {
_ = ctx
key := tenantProfileKey(tenantID, userID, scope)
s.mu.RLock()
defer s.mu.RUnlock()
profile, ok := s.xworkmateProfiles[key]
if !ok {
return nil, ErrXWorkmateProfileNotFound
}
entry := *profile
return &entry, nil
}
func (s *memoryStore) UpsertXWorkmateProfile(ctx context.Context, profile *XWorkmateProfile) error {
_ = ctx
if profile == nil {
return ErrXWorkmateProfileNotFound
}
NormalizeXWorkmateProfile(profile)
if profile.TenantID == "" {
return ErrXWorkmateProfileNotFound
}
if profile.Scope == XWorkmateProfileScopeUserPrivate && profile.UserID == "" {
return ErrXWorkmateProfileNotFound
}
if profile.Scope == XWorkmateProfileScopeTenantShared {
profile.UserID = ""
}
if profile.ID == "" {
profile.ID = uuid.NewString()
}
now := time.Now().UTC()
key := tenantProfileKey(profile.TenantID, profile.UserID, profile.Scope)
s.mu.Lock()
defer s.mu.Unlock()
if existing, ok := s.xworkmateProfiles[key]; ok {
existing.OpenclawURL = profile.OpenclawURL
existing.OpenclawOrigin = profile.OpenclawOrigin
existing.VaultURL = profile.VaultURL
existing.VaultNamespace = profile.VaultNamespace
existing.VaultSecretPath = profile.VaultSecretPath
existing.VaultSecretKey = profile.VaultSecretKey
existing.ApisixURL = profile.ApisixURL
existing.UpdatedAt = now
profile.CreatedAt = existing.CreatedAt
profile.UpdatedAt = existing.UpdatedAt
return nil
}
stored := &XWorkmateProfile{
ID: profile.ID,
TenantID: profile.TenantID,
UserID: profile.UserID,
Scope: profile.Scope,
OpenclawURL: profile.OpenclawURL,
OpenclawOrigin: profile.OpenclawOrigin,
VaultURL: profile.VaultURL,
VaultNamespace: profile.VaultNamespace,
VaultSecretPath: profile.VaultSecretPath,
VaultSecretKey: profile.VaultSecretKey,
ApisixURL: profile.ApisixURL,
CreatedAt: now,
UpdatedAt: now,
}
s.xworkmateProfiles[key] = stored
profile.CreatedAt = stored.CreatedAt
profile.UpdatedAt = stored.UpdatedAt
return nil
}