oups
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/leolionad58/ticketbot/internal/db"
|
||||
panelauth "github.com/leolionad58/ticketbot/internal/panel/auth"
|
||||
)
|
||||
|
||||
// handleAdminCLI processes "ticketbot admin <subcommand>" and exits.
|
||||
// Returns true if an admin subcommand was handled.
|
||||
func handleAdminCLI(sqldb *sql.DB) bool {
|
||||
if len(os.Args) < 2 || os.Args[1] != "admin" {
|
||||
return false
|
||||
}
|
||||
|
||||
repo := db.NewPanelAdminRepo(sqldb)
|
||||
ctx := context.Background()
|
||||
|
||||
if len(os.Args) < 3 {
|
||||
printAdminUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch os.Args[2] {
|
||||
case "add":
|
||||
cliAddAdmin(ctx, repo)
|
||||
case "remove":
|
||||
cliRemoveAdmin(ctx, repo)
|
||||
case "list":
|
||||
cliListAdmins(ctx, repo)
|
||||
case "reset-totp":
|
||||
cliResetTOTP(ctx, repo)
|
||||
case "reset-password":
|
||||
cliResetPassword(ctx, repo)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown admin subcommand: %s\n", os.Args[2])
|
||||
printAdminUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cliAddAdmin(ctx context.Context, repo *db.PanelAdminRepo) {
|
||||
args := os.Args[3:]
|
||||
discordID := flagValue(args, "--discord-id")
|
||||
password := flagValue(args, "--password")
|
||||
superadmin := containsFlag(args, "--superadmin")
|
||||
if discordID == "" {
|
||||
fmt.Fprintln(os.Stderr, "Usage: admin add --discord-id <id> [--password <pass>] [--superadmin]")
|
||||
os.Exit(1)
|
||||
}
|
||||
var hash string
|
||||
if password != "" {
|
||||
var err error
|
||||
hash, err = panelauth.HashPassword(password)
|
||||
must(err, "hash password")
|
||||
}
|
||||
a, err := repo.Create(ctx, discordID, "", hash, superadmin)
|
||||
must(err, "create admin")
|
||||
fmt.Printf("Admin added: discord_id=%s id=%d superadmin=%v\n", a.DiscordID, a.ID, a.IsSuperadmin)
|
||||
}
|
||||
|
||||
func cliRemoveAdmin(ctx context.Context, repo *db.PanelAdminRepo) {
|
||||
discordID := flagValue(os.Args[3:], "--discord-id")
|
||||
if discordID == "" {
|
||||
fmt.Fprintln(os.Stderr, "Usage: admin remove --discord-id <id>")
|
||||
os.Exit(1)
|
||||
}
|
||||
must(repo.Delete(ctx, discordID), "remove admin")
|
||||
fmt.Printf("Admin removed: %s\n", discordID)
|
||||
}
|
||||
|
||||
func cliListAdmins(ctx context.Context, repo *db.PanelAdminRepo) {
|
||||
admins, err := repo.List(ctx)
|
||||
must(err, "list admins")
|
||||
if len(admins) == 0 {
|
||||
fmt.Println("No admins configured.")
|
||||
return
|
||||
}
|
||||
fmt.Printf("%-20s %-30s %-12s %-12s\n", "Discord ID", "Username", "Superadmin", "TOTP")
|
||||
for _, a := range admins {
|
||||
fmt.Printf("%-20s %-30s %-12v %-12v\n", a.DiscordID, a.DiscordUsername, a.IsSuperadmin, a.TOTPEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
func cliResetTOTP(ctx context.Context, repo *db.PanelAdminRepo) {
|
||||
discordID := flagValue(os.Args[3:], "--discord-id")
|
||||
if discordID == "" {
|
||||
fmt.Fprintln(os.Stderr, "Usage: admin reset-totp --discord-id <id>")
|
||||
os.Exit(1)
|
||||
}
|
||||
a, err := repo.GetByDiscordID(ctx, discordID)
|
||||
must(err, "get admin")
|
||||
if a == nil {
|
||||
fmt.Fprintf(os.Stderr, "Admin not found: %s\n", discordID)
|
||||
os.Exit(1)
|
||||
}
|
||||
must(repo.ResetTOTP(ctx, a.ID), "reset totp")
|
||||
fmt.Printf("TOTP reset for %s — admin must re-setup TOTP on next login.\n", discordID)
|
||||
}
|
||||
|
||||
func cliResetPassword(ctx context.Context, repo *db.PanelAdminRepo) {
|
||||
args := os.Args[3:]
|
||||
discordID := flagValue(args, "--discord-id")
|
||||
password := flagValue(args, "--password")
|
||||
if discordID == "" || password == "" {
|
||||
fmt.Fprintln(os.Stderr, "Usage: admin reset-password --discord-id <id> --password <pass>")
|
||||
os.Exit(1)
|
||||
}
|
||||
a, err := repo.GetByDiscordID(ctx, discordID)
|
||||
must(err, "get admin")
|
||||
if a == nil {
|
||||
fmt.Fprintf(os.Stderr, "Admin not found: %s\n", discordID)
|
||||
os.Exit(1)
|
||||
}
|
||||
hash, err := panelauth.HashPassword(password)
|
||||
must(err, "hash password")
|
||||
must(repo.UpdatePassword(ctx, a.ID, hash), "update password")
|
||||
fmt.Printf("Password updated for %s\n", discordID)
|
||||
}
|
||||
|
||||
func flagValue(args []string, flag string) string {
|
||||
for i := 0; i < len(args)-1; i++ {
|
||||
if args[i] == flag {
|
||||
return args[i+1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func containsFlag(args []string, flag string) bool {
|
||||
for _, a := range args {
|
||||
if a == flag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func printAdminUsage() {
|
||||
fmt.Fprintln(os.Stderr, `Usage: ticketbot admin <subcommand> [flags]
|
||||
|
||||
Subcommands:
|
||||
add --discord-id <id> --password <pass> [--superadmin]
|
||||
remove --discord-id <id>
|
||||
list
|
||||
reset-totp --discord-id <id>
|
||||
reset-password --discord-id <id> --password <pass>`)
|
||||
}
|
||||
|
||||
func newAdminRepo(sqldb *sql.DB) *db.PanelAdminRepo {
|
||||
return db.NewPanelAdminRepo(sqldb)
|
||||
}
|
||||
+72
-7
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/leolionad58/ticketbot/internal/discord/components"
|
||||
"github.com/leolionad58/ticketbot/internal/discord/events"
|
||||
"github.com/leolionad58/ticketbot/internal/logger"
|
||||
"github.com/leolionad58/ticketbot/internal/panel"
|
||||
"github.com/leolionad58/ticketbot/internal/tickets"
|
||||
"github.com/leolionad58/ticketbot/internal/transcript"
|
||||
)
|
||||
@@ -34,8 +36,23 @@ func main() {
|
||||
must(err, "open db")
|
||||
defer sqldb.Close()
|
||||
|
||||
// CLI admin subcommand — handle and exit before starting the bot
|
||||
if handleAdminCLI(sqldb) {
|
||||
return
|
||||
}
|
||||
|
||||
// Migrate YAML panels to DB on first boot
|
||||
panelRepo := db.NewPanelConfigRepo(sqldb)
|
||||
if err := panel.MigrateYAMLPanels(context.Background(), panelRepo, cfg, os.Getenv("GUILD_ID")); err != nil {
|
||||
slog.Warn("YAML panel migration failed", "err", err)
|
||||
}
|
||||
|
||||
ticketRepo := db.NewTicketRepo(sqldb)
|
||||
claimRepo := db.NewClaimMessageRepo(sqldb)
|
||||
convocRepo := db.NewConvocationConfigRepo(sqldb)
|
||||
adminRepo := db.NewPanelAdminRepo(sqldb)
|
||||
sessionRepo := db.NewPanelSessionRepo(sqldb)
|
||||
auditRepo := db.NewAuditLogRepo(sqldb)
|
||||
|
||||
token := os.Getenv("DISCORD_TOKEN")
|
||||
if token == "" {
|
||||
@@ -45,12 +62,27 @@ func main() {
|
||||
appID := os.Getenv("DISCORD_APP_ID")
|
||||
guildID := os.Getenv("GUILD_ID")
|
||||
|
||||
bot, err := discordbot.New(token, cfgProvider, ticketRepo, claimRepo, guildID)
|
||||
// Build guild allowlist: config yaml takes priority, env GUILD_ID is always included
|
||||
allowedGuilds := append([]string{}, cfg.Bot.AllowedGuilds...)
|
||||
if guildID != "" {
|
||||
found := false
|
||||
for _, id := range allowedGuilds {
|
||||
if id == guildID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
allowedGuilds = append(allowedGuilds, guildID)
|
||||
}
|
||||
}
|
||||
|
||||
bot, err := discordbot.New(token, cfgProvider, ticketRepo, claimRepo, guildID, allowedGuilds)
|
||||
must(err, "create bot")
|
||||
|
||||
// Services
|
||||
auth := tickets.NewAuthService(cfgProvider)
|
||||
ticketSvc := tickets.NewService(ticketRepo, auth, bot.Session, cfgProvider)
|
||||
ticketSvc := tickets.NewService(ticketRepo, panelRepo, auth, bot.Session, cfgProvider)
|
||||
claimMgr := claim.NewManager(cfgProvider, ticketRepo, claimRepo)
|
||||
logSvc := logger.NewDiscordLogger(bot.Session, cfgProvider)
|
||||
transcriptGen := transcript.NewGenerator("transcripts", bot.Session)
|
||||
@@ -76,6 +108,7 @@ func main() {
|
||||
panelComp := &components.PanelComponent{
|
||||
TicketSvc: ticketSvc,
|
||||
TicketRepo: ticketRepo,
|
||||
PanelRepo: panelRepo,
|
||||
ClaimMgr: claimMgr,
|
||||
LogSvc: logSvc,
|
||||
Config: cfgProvider,
|
||||
@@ -88,14 +121,22 @@ func main() {
|
||||
Transcript: transcriptGen,
|
||||
Auth: auth,
|
||||
}
|
||||
convocComp := &components.ConvocationComponent{
|
||||
ConvocRepo: convocRepo,
|
||||
TicketRepo: ticketRepo,
|
||||
Auth: auth,
|
||||
LogSvc: logSvc,
|
||||
}
|
||||
|
||||
// Router
|
||||
router := &discordbot.Router{
|
||||
PanelCmd: panelCmd,
|
||||
TicketCmd: ticketCmd,
|
||||
ConvocCmd: convocCmd,
|
||||
PanelComp: panelComp,
|
||||
TicketComp: ticketComp,
|
||||
PanelCmd: panelCmd,
|
||||
TicketCmd: ticketCmd,
|
||||
ConvocCmd: convocCmd,
|
||||
PanelComp: panelComp,
|
||||
TicketComp: ticketComp,
|
||||
ConvocComp: convocComp,
|
||||
IsGuildAllowed: bot.IsGuildAllowed,
|
||||
}
|
||||
bot.Session.AddHandler(router.Handle)
|
||||
|
||||
@@ -123,6 +164,30 @@ func main() {
|
||||
slog.Warn("config watcher failed to start", "err", err)
|
||||
}
|
||||
|
||||
// Start panel HTTP server if configured
|
||||
if cfg.Panel.BaseURL != "" || cfg.Panel.Port > 0 {
|
||||
botSvc := discordbot.NewBotService(bot.Session, panelRepo, ticketRepo, convocRepo, guildID, allowedGuilds)
|
||||
panelSrv := panel.NewServer(
|
||||
cfg.Panel,
|
||||
ticketRepo,
|
||||
adminRepo,
|
||||
sessionRepo,
|
||||
auditRepo,
|
||||
panelRepo,
|
||||
convocRepo,
|
||||
guildID,
|
||||
botSvc,
|
||||
)
|
||||
ctx, cancelPanel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
slog.Info("panel server starting", "port", cfg.Panel.Port)
|
||||
if err := panelSrv.Start(ctx); err != nil {
|
||||
slog.Error("panel server stopped", "err", err)
|
||||
}
|
||||
}()
|
||||
defer cancelPanel()
|
||||
}
|
||||
|
||||
slog.Info("ticketbot ready, Ctrl+C to stop")
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
Reference in New Issue
Block a user