Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
112 lines
2.9 KiB
Go
112 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/descrybe/descrybe-v2/apps/api/internal/config"
|
|
"github.com/descrybe/descrybe-v2/apps/api/internal/mail"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Hook matches migrator maps/set-password-hooks.json (invite tokens for must_set_password users).
|
|
type Hook struct {
|
|
UserID uuid.UUID `json:"user_id"`
|
|
Email string `json:"email"`
|
|
CompanyID uuid.UUID `json:"company_id"`
|
|
Role string `json:"role"`
|
|
Token string `json:"token"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
InviteID uuid.UUID `json:"invite_id"`
|
|
}
|
|
|
|
func main() {
|
|
hooksPath := flag.String("hooks", "", "Path to set-password-hooks.json from migrator maps-dir")
|
|
dryRun := flag.Bool("dry-run", false, "Print counts without sending")
|
|
delayMS := flag.Int("delay-ms", 100, "Pause between sends (SMTP rate limit)")
|
|
flag.Parse()
|
|
|
|
if *hooksPath == "" {
|
|
log.Fatal("-hooks is required (e.g. ../../artifacts/maps/set-password-hooks.json)")
|
|
}
|
|
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
log.Fatalf("config: %v", err)
|
|
}
|
|
|
|
raw, err := os.ReadFile(*hooksPath)
|
|
if err != nil {
|
|
log.Fatalf("read hooks: %v", err)
|
|
}
|
|
var hooks []Hook
|
|
if err := json.Unmarshal(raw, &hooks); err != nil {
|
|
log.Fatalf("parse hooks: %v", err)
|
|
}
|
|
|
|
mailer := mail.New(mail.Config{
|
|
Enabled: cfg.SMTPEnabled,
|
|
Host: cfg.SMTPHost,
|
|
Port: cfg.SMTPPort,
|
|
User: cfg.SMTPUser,
|
|
Password: cfg.SMTPPassword,
|
|
From: cfg.SMTPFrom,
|
|
})
|
|
if err := assertMailhooksReady(*dryRun, mailer.Enabled(), cfg.EmailDryRun); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
sent, skipped, failed := 0, 0, 0
|
|
now := time.Now().UTC()
|
|
for _, h := range hooks {
|
|
if h.Token == "" || h.Email == "" {
|
|
skipped++
|
|
continue
|
|
}
|
|
if !h.ExpiresAt.IsZero() && now.After(h.ExpiresAt) {
|
|
skipped++
|
|
continue
|
|
}
|
|
msg := mail.MigratedSetPasswordMessage(cfg.WebOrigin, h.Email, h.Token)
|
|
if *dryRun {
|
|
log.Printf("mailhooks: dry-run subject=%q", msg.Subject)
|
|
sent++
|
|
continue
|
|
}
|
|
if err := mailer.Send(msg); err != nil {
|
|
log.Printf("mailhooks: send failed subject=%q", msg.Subject)
|
|
failed++
|
|
continue
|
|
}
|
|
sent++
|
|
if *delayMS > 0 {
|
|
time.Sleep(time.Duration(*delayMS) * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
fmt.Printf("mailhooks: sent=%d skipped=%d failed=%d smtp_enabled=%v total=%d\n",
|
|
sent, skipped, failed, mailer.Enabled(), len(hooks))
|
|
if failed > 0 {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// assertMailhooksReady fails closed for live sends when SMTP is a no-op or EMAIL_DRY_RUN is on.
|
|
func assertMailhooksReady(dryRun, mailerEnabled, emailDryRun bool) error {
|
|
if dryRun {
|
|
return nil
|
|
}
|
|
if emailDryRun {
|
|
return errors.New("mailhooks: email dry-run is on; pass -dry-run or disable dry-run in admin platform mail settings (or EMAIL_DRY_RUN=false)")
|
|
}
|
|
if !mailerEnabled {
|
|
return errors.New("mailhooks: SMTP not configured; pass -dry-run or set platform mail SMTP in admin (smtp.enabled + host/from)")
|
|
}
|
|
return nil
|
|
}
|