feat(ops): чистка данных, reindex и PWA с офлайн-режимом

- internal/retention: истёкшие сессии, аудит старше RETENTION_AUDIT_DAYS и
  файлы без ссылок старше RETENTION_ORPHAN_HOURS; сиротой файл считается
  только если на него не ссылается ничего (вложения, аватары, оформление
  сервера, комнат, ролей, эмодзи, звуки, вебхуки, приглашения)
- обслуживание запускается в приложении по RETENTION_INTERVAL_SECONDS;
  команды glchat cleanup и glchat reindex делают то же вручную
- PWA: манифест с иконками и shortcuts, офлайн-страница, сервис-воркер
  (кэш только /assets/*, приватные запросы не кэшируются)
- тесты: Go на чистку (сирота удаляется, используемые файлы и свежие сироты
  остаются, истёкшая сессия уходит), Vitest на манифест и регистрацию воркера
This commit is contained in:
2026-09-21 19:42:12 +03:00
parent 72537e9ff9
commit 5afcfee744
19 changed files with 813 additions and 12 deletions
+15 -3
View File
@@ -18,6 +18,7 @@ import (
"glchat/internal/database"
"glchat/internal/gateway"
"glchat/internal/permissions"
"glchat/internal/retention"
"glchat/internal/server"
"glchat/internal/source"
"glchat/internal/store"
@@ -37,11 +38,12 @@ func main() {
os.Exit(healthcheck())
}
// Подкоманды обслуживания (AGENT.md 10.7): bootstrap-admin, reset-password,
// make-admin, remove-admin, totp-setup, totp-reset. Остальные команды
// реализует shell-обёртка glchat.
// make-admin, remove-admin, totp-setup, totp-reset, cleanup, reindex.
// Остальные команды реализует shell-обёртка glchat.
if len(os.Args) > 1 {
switch os.Args[1] {
case "bootstrap-admin", "reset-password", "make-admin", "remove-admin", "totp-setup", "totp-reset":
case "bootstrap-admin", "reset-password", "make-admin", "remove-admin", "totp-setup", "totp-reset",
"cleanup", "reindex":
os.Exit(runCLI(os.Args[1:]))
}
}
@@ -163,6 +165,16 @@ func run() error {
srv := server.New(cfg, db, logger, server.Deps{
Store: st, Auth: authService, Gateway: gatewayService, Permissions: calculator,
})
// Чистка данных инстанса: сессии, аудит и файлы без ссылок (AGENT.md 6.4).
cleaner := retention.New(st, retention.Options{
DataDir: cfg.DataDir,
AuditDays: cfg.RetainAuditDays,
OrphanHours: cfg.RetainOrphanHours,
Logger: logger,
})
stopRetention := cleaner.Run(ctx, cfg.RetainInterval)
defer stopRetention()
// Сторож голосовых состояний: убирает «призрачные» записи после сбоев.
srv.StartVoiceWatchdog(context.Background(), 0)
errCh := make(chan error, 1)