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:
+26
-6
@@ -96,16 +96,36 @@ func (s *Store) DeleteFile(ctx context.Context, id uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListOrphanFiles возвращает файлы без сообщения старше указанного времени:
|
||||
// их удаляет обслуживание (AGENT.md 7.7).
|
||||
// orphanFilesQuery — файлы, на которые никто не ссылается: вложения сообщений,
|
||||
// аватары и баннеры пользователей, оформление сервера, роли и комнаты, эмодзи,
|
||||
// звуки, вебхуки и приглашения (AGENT.md 6.4, 7.7).
|
||||
const orphanFilesQuery = `
|
||||
SELECT ` + fileColumns + `
|
||||
FROM files f
|
||||
WHERE f.created_at <= ?
|
||||
AND f.message_id IS NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM users u WHERE u.avatar_file_id = f.id OR u.banner_file_id = f.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM guilds g WHERE g.icon_file_id = f.id OR g.banner_file_id = f.id OR g.splash_file_id = f.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM channels c WHERE c.background_file_id = f.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM invites i WHERE i.background_file_id = f.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM guild_cosmetics gc WHERE gc.file_id = f.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM webhooks w WHERE w.avatar_file_id = f.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM guild_emojis ge WHERE ge.file_id = f.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM guild_sounds gs WHERE gs.file_id = f.id)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM messages m, json_each(m.attachments_json) a
|
||||
WHERE json_extract(a.value, '$.file_id') = f.id
|
||||
)
|
||||
ORDER BY f.id
|
||||
LIMIT ?`
|
||||
|
||||
// ListOrphanFiles возвращает файлы без ссылок старше указанного времени: их
|
||||
// удаляет обслуживание (AGENT.md 6.4, 7.7).
|
||||
func (s *Store) ListOrphanFiles(ctx context.Context, olderThan time.Time, limit int) ([]File, error) {
|
||||
if limit <= 0 || limit > 500 {
|
||||
limit = 100
|
||||
}
|
||||
rows, err := s.reader.QueryContext(ctx, `
|
||||
SELECT `+fileColumns+` FROM files
|
||||
WHERE message_id IS NULL AND created_at < ? ORDER BY id LIMIT ?`,
|
||||
s.Timestamp(olderThan), limit)
|
||||
rows, err := s.reader.QueryContext(ctx, orphanFilesQuery, s.Timestamp(olderThan), limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user