Files
grendervill a5205cc054 feat(store,permissions): схема Фазы 1, доступ к данным и движок прав
- миграция 00002: users, sessions, totp_secrets, webauthn_credentials,
  security_events, guilds, guild_members, roles, member_roles, channels,
  channel_overrides, audit_log + дефолтные instance_settings (AGENT.md 6.1)
- internal/store: Snowflake-идентификаторы, CRUD пользователей и сессий
  (ротация, step-up, logout-all), TOTP и события безопасности, серверы,
  участники, роли, комнаты и оверрайды, настройки инстанса и аудит
- internal/permissions: 37 прав битмаской, вычисление по правилам §6.2
  (баз role @user → оверрайды ролей → оверрайд пользователя → ADMINISTRATOR),
  иерархия ролей и участников, тайм-ауты, обход для инстанс-админа, LRU-кэш
  с инвалидацией
- internal/source: адаптер permissions.Source поверх store
- тесты: 18 unit-тестов прав + интеграционный набор на реальной SQLite
  (приватная комната, модератор, владелец, инстанс-админ, тайм-аут)
- golangci: обоснованное исключение gosec для пакета store (конверсии
  Snowflake и сборка SQL из константных шаблонов)
2026-09-19 21:24:17 +03:00

68 lines
1.8 KiB
Go

package store
import (
"sync"
"time"
)
// Snowflake — генератор сортируемых идентификаторов (AGENT.md 6.4):
// 41 бит времени от эпохи 2026-01-01T00:00:00Z, 10 бит узла, 12 бит счётчика.
type Snowflake struct {
mu sync.Mutex
epoch int64
node int64
lastMs int64
sequence int64
now func() time.Time
}
const (
snowflakeEpochMs = int64(1767225600000) // 2026-01-01T00:00:00Z
nodeBits = 10
sequenceBits = 12
maxNode = (1 << nodeBits) - 1
maxSequence = (1 << sequenceBits) - 1
)
func NewSnowflake(node uint16) *Snowflake {
return &Snowflake{
epoch: snowflakeEpochMs,
node: int64(node) & maxNode,
now: func() time.Time { return time.Now().UTC() },
}
}
// Next возвращает монотонно возрастающий идентификатор.
func (s *Snowflake) Next() uint64 {
s.mu.Lock()
defer s.mu.Unlock()
nowMs := s.now().UnixMilli()
if nowMs < s.lastMs {
// Часы ушли назад: продолжаем с последней метки, чтобы не выдать дубль.
nowMs = s.lastMs
}
if nowMs == s.lastMs {
s.sequence = (s.sequence + 1) & maxSequence
if s.sequence == 0 {
// Счётчик переполнен в эту миллисекунду — ждём следующую.
for nowMs <= s.lastMs {
time.Sleep(time.Millisecond)
nowMs = s.now().UnixMilli()
}
}
} else {
s.sequence = 0
}
s.lastMs = nowMs
id := ((nowMs - s.epoch) << (nodeBits + sequenceBits)) | (s.node << sequenceBits) | s.sequence
return uint64(id)
}
// SnowflakeTime возвращает время создания идентификатора.
func SnowflakeTime(id uint64) time.Time {
ms := int64(id>>(nodeBits+sequenceBits)) + snowflakeEpochMs
return time.UnixMilli(ms).UTC()
}