style(shared-sqs): fix indentation in tenant_store.go

This commit is contained in:
“Naeel”
2026-04-09 20:43:17 +03:00
parent 570d1e3a60
commit ab1e47bd35
+104 -104
View File
@@ -3,165 +3,165 @@
package tenant package tenant
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"sync" "sync"
"time" "time"
) )
// Tenant — модель тенанта shared-sqs. // Tenant — модель тенанта shared-sqs.
// AccessKey используется как идентификатор в AWS Authorization header. // AccessKey используется как идентификатор в AWS Authorization header.
type Tenant struct { type Tenant struct {
ID string // уникальный идентификатор тенанта (t-<hex>) ID string // уникальный идентификатор тенанта (t-<hex>)
Name string // человекочитаемое имя Name string // человекочитаемое имя
AccessKey string // аналог AWS AccessKeyId (SSAK-<hex>) AccessKey string // аналог AWS AccessKeyId (SSAK-<hex>)
SecretKey string // аналог AWS SecretAccessKey (64 hex chars) SecretKey string // аналог AWS SecretAccessKey (64 hex chars)
MaxQueues int // лимит очередей (0 = безлимит) MaxQueues int // лимит очередей (0 = безлимит)
CreatedAt time.Time CreatedAt time.Time
Active bool Active bool
} }
// TenantStore — потокобезопасное in-memory хранилище тенантов. // TenantStore — потокобезопасное in-memory хранилище тенантов.
// Два индекса позволяют быстро искать как по ID (admin API), так и по AccessKey (auth middleware). // Два индекса позволяют быстро искать как по ID (admin API), так и по AccessKey (auth middleware).
type TenantStore struct { type TenantStore struct {
mu sync.RWMutex mu sync.RWMutex
byID map[string]*Tenant byID map[string]*Tenant
byAccessKey map[string]*Tenant byAccessKey map[string]*Tenant
} }
// NewTenantStore — создаёт пустое хранилище тенантов. // NewTenantStore — создаёт пустое хранилище тенантов.
func NewTenantStore() *TenantStore { func NewTenantStore() *TenantStore {
return &TenantStore{ return &TenantStore{
byID: make(map[string]*Tenant), byID: make(map[string]*Tenant),
byAccessKey: make(map[string]*Tenant), byAccessKey: make(map[string]*Tenant),
} }
} }
// Create — создаёт нового тенанта, генерирует ключи, сохраняет в оба индекса. // Create — создаёт нового тенанта, генерирует ключи, сохраняет в оба индекса.
func (s *TenantStore) Create(name string, maxQueues int) (*Tenant, error) { func (s *TenantStore) Create(name string, maxQueues int) (*Tenant, error) {
id, err := generateTenantID() id, err := generateTenantID()
if err != nil { if err != nil {
return nil, fmt.Errorf("generate tenant id: %w", err) return nil, fmt.Errorf("generate tenant id: %w", err)
} }
accessKey, err := generateAccessKey() accessKey, err := generateAccessKey()
if err != nil { if err != nil {
return nil, fmt.Errorf("generate access key: %w", err) return nil, fmt.Errorf("generate access key: %w", err)
} }
secretKey, err := generateSecretKey() secretKey, err := generateSecretKey()
if err != nil { if err != nil {
return nil, fmt.Errorf("generate secret key: %w", err) return nil, fmt.Errorf("generate secret key: %w", err)
} }
t := &Tenant{ t := &Tenant{
ID: id, ID: id,
Name: name, Name: name,
AccessKey: accessKey, AccessKey: accessKey,
SecretKey: secretKey, SecretKey: secretKey,
MaxQueues: maxQueues, MaxQueues: maxQueues,
CreatedAt: time.Now().UTC(), CreatedAt: time.Now().UTC(),
Active: true, Active: true,
} }
s.mu.Lock() s.mu.Lock()
s.byID[t.ID] = t s.byID[t.ID] = t
s.byAccessKey[t.AccessKey] = t s.byAccessKey[t.AccessKey] = t
s.mu.Unlock() s.mu.Unlock()
return t, nil return t, nil
} }
// CreateFixed — создаёт тенанта с заранее известными credentials (для seed/demo). // CreateFixed — создаёт тенанта с заранее известными credentials (для seed/demo).
// Используется только при инициализации демо-данных; не вызывается из user-facing API. // Используется только при инициализации демо-данных; не вызывается из user-facing API.
func (s *TenantStore) CreateFixed(name string, maxQueues int, tenantID, accessKey, secretKey string) (*Tenant, error) { func (s *TenantStore) CreateFixed(name string, maxQueues int, tenantID, accessKey, secretKey string) (*Tenant, error) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if _, exists := s.byID[tenantID]; exists { if _, exists := s.byID[tenantID]; exists {
return nil, fmt.Errorf("tenant with id %s already exists", tenantID) return nil, fmt.Errorf("tenant with id %s already exists", tenantID)
} }
if _, exists := s.byAccessKey[accessKey]; exists { if _, exists := s.byAccessKey[accessKey]; exists {
return nil, fmt.Errorf("tenant with access key %s already exists", accessKey) return nil, fmt.Errorf("tenant with access key %s already exists", accessKey)
} }
t := &Tenant{ t := &Tenant{
ID: tenantID, ID: tenantID,
Name: name, Name: name,
AccessKey: accessKey, AccessKey: accessKey,
SecretKey: secretKey, SecretKey: secretKey,
MaxQueues: maxQueues, MaxQueues: maxQueues,
CreatedAt: time.Now().UTC(), CreatedAt: time.Now().UTC(),
Active: true, Active: true,
} }
s.byID[t.ID] = t s.byID[t.ID] = t
s.byAccessKey[t.AccessKey] = t s.byAccessKey[t.AccessKey] = t
return t, nil return t, nil
} }
// GetByAccessKey — поиск тенанта по AccessKeyId (используется в auth middleware). // GetByAccessKey — поиск тенанта по AccessKeyId (используется в auth middleware).
func (s *TenantStore) GetByAccessKey(accessKey string) (*Tenant, bool) { func (s *TenantStore) GetByAccessKey(accessKey string) (*Tenant, bool) {
s.mu.RLock() s.mu.RLock()
t, ok := s.byAccessKey[accessKey] t, ok := s.byAccessKey[accessKey]
s.mu.RUnlock() s.mu.RUnlock()
return t, ok return t, ok
} }
// GetByID — поиск тенанта по ID (используется в admin API). // GetByID — поиск тенанта по ID (используется в admin API).
func (s *TenantStore) GetByID(id string) (*Tenant, bool) { func (s *TenantStore) GetByID(id string) (*Tenant, bool) {
s.mu.RLock() s.mu.RLock()
t, ok := s.byID[id] t, ok := s.byID[id]
s.mu.RUnlock() s.mu.RUnlock()
return t, ok return t, ok
} }
// Delete — удаляет тенанта из ОБОИХ индексов. // Delete — удаляет тенанта из ОБОИХ индексов.
// Ловушка #2: если удалить только из одного индекса — orphaned данные и memory leak. // Ловушка #2: если удалить только из одного индекса — orphaned данные и memory leak.
func (s *TenantStore) Delete(id string) bool { func (s *TenantStore) Delete(id string) bool {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
t, ok := s.byID[id] t, ok := s.byID[id]
if !ok { if !ok {
return false return false
} }
delete(s.byID, t.ID) delete(s.byID, t.ID)
delete(s.byAccessKey, t.AccessKey) delete(s.byAccessKey, t.AccessKey)
return true return true
} }
// List — список всех тенантов (для admin GET /tenants). // List — список всех тенантов (для admin GET /tenants).
func (s *TenantStore) List() []*Tenant { func (s *TenantStore) List() []*Tenant {
s.mu.RLock() s.mu.RLock()
result := make([]*Tenant, 0, len(s.byID)) result := make([]*Tenant, 0, len(s.byID))
for _, t := range s.byID { for _, t := range s.byID {
result = append(result, t) result = append(result, t)
} }
s.mu.RUnlock() s.mu.RUnlock()
return result return result
} }
// generateTenantID — генерирует уникальный ID тенанта в формате t-<12 hex bytes>. // generateTenantID — генерирует уникальный ID тенанта в формате t-<12 hex bytes>.
func generateTenantID() (string, error) { func generateTenantID() (string, error) {
b := make([]byte, 8) b := make([]byte, 8)
if _, err := rand.Read(b); err != nil { if _, err := rand.Read(b); err != nil {
return "", err return "", err
} }
return "t-" + hex.EncodeToString(b), nil return "t-" + hex.EncodeToString(b), nil
} }
// generateAccessKey — генерирует AccessKey в формате SSAK-<12 hex bytes>. // generateAccessKey — генерирует AccessKey в формате SSAK-<12 hex bytes>.
// SSAK = Shared SQS Access Key. Используем crypto/rand (ловушка #1: не math/rand). // SSAK = Shared SQS Access Key. Используем crypto/rand (ловушка #1: не math/rand).
func generateAccessKey() (string, error) { func generateAccessKey() (string, error) {
b := make([]byte, 12) b := make([]byte, 12)
if _, err := rand.Read(b); err != nil { if _, err := rand.Read(b); err != nil {
return "", err return "", err
} }
return "SSAK-" + hex.EncodeToString(b), nil return "SSAK-" + hex.EncodeToString(b), nil
} }
// generateSecretKey — генерирует SecretKey как 64 hex символа (32 random bytes). // generateSecretKey — генерирует SecretKey как 64 hex символа (32 random bytes).
// Используем crypto/rand (ловушка #1). // Используем crypto/rand (ловушка #1).
func generateSecretKey() (string, error) { func generateSecretKey() (string, error) {
b := make([]byte, 32) b := make([]byte, 32)
if _, err := rand.Read(b); err != nil { if _, err := rand.Read(b); err != nil {
return "", err return "", err
} }
return hex.EncodeToString(b), nil return hex.EncodeToString(b), nil
} }