fix(cli,web): команды аккаунтов в CLI и повтор запросов при 429

- deploy/glchat-cli.sh: reset-password/make-admin/remove-admin больше не
  заглушки «появится в фазе 1» (AGENT.md 10.7), добавлены totp-setup,
  totp-reset и delete-user; подкоманды обслуживания идут через общий app_cli
- cmd/glchat: команда delete-user (мягкое удаление как в админ-панели,
  защита от удаления инстанс-админа и подтверждение --yes), исправлен
  потерянный комментарий cliCleanup
- deploy/install.sh: убраны устаревшие формулировки про фазу 1
- web: политика повторов запросов — 429 повторяется с паузой retry_after_ms
  (до двух раз, не дольше 5 с), 4xx не повторяются; тесты web/tests/queryRetry.test.ts
- scripts/test-install.sh: проверка, что заглушек в CLI не осталось
This commit is contained in:
2026-09-26 18:32:51 +03:00
parent e7c9c93443
commit b9d458e133
7 changed files with 265 additions and 22 deletions
+47 -2
View File
@@ -38,6 +38,8 @@ func runCLI(args []string) int {
return cliTOTPReset(args[1:])
case "create-user":
return cliCreateUser(args[1:])
case "delete-user":
return cliDeleteUser(args[1:])
case "cleanup":
return cliCleanup(args[1:])
case "reindex":
@@ -278,8 +280,6 @@ func cliSetInstanceAdmin(command string, args []string) int {
})
}
// cliCleanup запускает чистку вручную: сессии, аудит и файлы без ссылок
// (AGENT.md 6.4, 10.7).
// cliCreateUser создаёт аккаунты по команде обслуживания: регистрация на
// инстансе может быть выключена, а для нагрузочного теста (AGENT.md 11.4)
// нужны десятки пользователей. Пароль задаёт оператор, аккаунты создаются без
@@ -381,6 +381,51 @@ func cliCreateUser(args []string) int {
return 0
}
// cliDeleteUser мягко удаляет аккаунт по команде обслуживания — ровно то же,
// что делает админ-панель инстанса (`DELETE /instance/users/{id}`): запись
// пользователя остаётся, но он исчезает из серверов, списков и сессий, а
// сообщения и журнал аудита сохраняются (AGENT.md 6.4). Аккаунт инстанс-админа
// удалить нельзя: это оставило бы инстанс без администратора — сначала
// `glchat remove-admin`.
func cliDeleteUser(args []string) int {
flags := flag.NewFlagSet("delete-user", flag.ContinueOnError)
email := flags.String("email", "", "email пользователя")
yes := flags.Bool("yes", false, "подтверждение удаления (без него команда только показывает, что будет удалено)")
positional := flags.Args()
if err := flags.Parse(args); err != nil {
return 2
}
if *email == "" && len(positional) > 0 {
*email = positional[0]
}
if *email == "" {
fmt.Fprintln(os.Stderr, "glchat delete-user: укажите email")
return 2
}
return withServices(func(ctx context.Context, st *store.Store, authService *auth.Service, _ config.Config) error {
user, err := authService.UserByEmail(ctx, *email)
if err != nil {
return fmt.Errorf("пользователь с таким email не найден")
}
if user.IsInstanceAdmin {
return fmt.Errorf("аккаунт %s — администратор инстанса: сначала снимите права командой glchat remove-admin", user.Username)
}
if !*yes {
fmt.Printf("будет удалён аккаунт %s <%s> (id %d): сессии отзываются, участие в серверах снимается,\n"+
"сообщения и журнал аудита остаются. Ничего не удалено — повторите с --yes.\n", user.Username, *email, user.ID)
return nil
}
if err := st.SoftDeleteUser(ctx, user.ID); err != nil {
return err
}
fmt.Printf("аккаунт %s <%s> удалён, сессии отозваны\n", user.Username, *email)
return nil
})
}
// cliCleanup запускает чистку вручную: сессии, аудит и файлы без ссылок
// (AGENT.md 6.4, 10.7).
func cliCleanup(args []string) int {
flags := flag.NewFlagSet("cleanup", flag.ContinueOnError)
auditDays := flags.Int("audit-days", -1, "сколько дней хранить журнал аудита (по умолчанию из конфигурации)")
+3 -3
View File
@@ -38,12 +38,12 @@ func main() {
os.Exit(healthcheck())
}
// Подкоманды обслуживания (AGENT.md 10.7): bootstrap-admin, reset-password,
// make-admin, remove-admin, totp-setup, totp-reset, cleanup, reindex.
// Остальные команды реализует shell-обёртка glchat.
// make-admin, remove-admin, totp-setup, totp-reset, create-user, delete-user,
// 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",
"create-user", "cleanup", "reindex", "vapid-keys":
"create-user", "delete-user", "cleanup", "reindex", "vapid-keys":
os.Exit(runCLI(os.Args[1:]))
}
}