a5205cc054
- миграция 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 из константных шаблонов)
68 lines
1.8 KiB
Go
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()
|
|
}
|