This commit is contained in:
lfirmin
2026-05-10 18:07:51 +02:00
parent 50af2452b1
commit a5f888b3c1
59 changed files with 6682 additions and 108 deletions
+252
View File
@@ -0,0 +1,252 @@
package handlers
import (
"net/http"
"strings"
"github.com/leolionad58/ticketbot/internal/db"
panelauth "github.com/leolionad58/ticketbot/internal/panel/auth"
)
// ConvocBotService is the subset of BotService needed for convocation operations.
type ConvocBotService interface {
CreateConvocation(guildID, staffDiscordID, targetDiscordID, reason string) (channelID string, err error)
SendConvocPanel(guildID string) error
SendConvocPanelByID(panelID int64) error
}
// ConvocDiscordInfo is the subset of DiscordInfo for guild selection in convocations.
type ConvocDiscordInfo interface {
GetGuildList() []GuildInfo
}
// ConvocationsHandler serves /convocations.
type ConvocationsHandler struct {
TicketRepo *db.TicketRepo
ConvocRepo *db.ConvocationConfigRepo
ConvocPanelRepo *db.ConvocationPanelRepo
GuildID string
BotService ConvocBotService
DiscordInfo ConvocDiscordInfo
Renderer *Renderer
}
type convocationsData struct {
baseData
Config *db.ConvocationConfig
ConvocPanels []*db.ConvocationPanel
Tickets []*db.Ticket
Total int
Page int
Pages int
Guilds []GuildInfo
}
// HandleGET serves GET /convocations.
func (h *ConvocationsHandler) HandleGET(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
base := h.Renderer.base(r, "convocations")
sess := panelauth.SessionFromContext(ctx)
if sess != nil {
base.CSRFToken = sess.CSRFToken
}
cfg, err := h.ConvocRepo.Get(ctx, h.GuildID)
if err != nil {
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
return
}
var convocPanels []*db.ConvocationPanel
if h.ConvocPanelRepo != nil {
convocPanels, _ = h.ConvocPanelRepo.List(ctx, h.GuildID)
}
page := 1
tickets, total, err := h.TicketRepo.ListFiltered(ctx, db.TicketFilter{
Type: "convocation",
Page: page,
PageSize: 20,
})
if err != nil {
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
return
}
pages := total / 20
if total%20 != 0 {
pages++
}
if pages < 1 {
pages = 1
}
var guilds []GuildInfo
if h.DiscordInfo != nil {
guilds = h.DiscordInfo.GetGuildList()
}
h.Renderer.Page(w, "convocations", convocationsData{
baseData: base,
Config: cfg,
ConvocPanels: convocPanels,
Tickets: tickets,
Total: total,
Page: page,
Pages: pages,
Guilds: guilds,
})
}
// HandlePOST serves POST /convocations — saves the convocation config.
func (h *ConvocationsHandler) HandlePOST(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
// Load existing config to preserve panel_message_id (not sent by form)
existing, _ := h.ConvocRepo.Get(ctx, h.GuildID)
panelMsgID := ""
panelChannelID := strings.TrimSpace(r.FormValue("panel_channel_id"))
if existing != nil {
panelMsgID = existing.PanelMessageID
// If channel changed, clear stored message ID so SendConvocPanel sends a new message
if existing.PanelChannelID != panelChannelID {
panelMsgID = ""
}
}
embedColor := strings.TrimSpace(r.FormValue("panel_embed_color"))
if embedColor == "" {
embedColor = "#5865f2"
}
cfg := &db.ConvocationConfig{
GuildID: h.GuildID,
CategoryID: strings.TrimSpace(r.FormValue("category_id")),
LogChannelID: strings.TrimSpace(r.FormValue("log_channel_id")),
ModalEnabled: r.FormValue("modal_enabled") == "1",
PanelChannelID: panelChannelID,
PanelMessageID: panelMsgID,
PanelEmbedTitle: strings.TrimSpace(r.FormValue("panel_embed_title")),
PanelEmbedDescription: strings.TrimSpace(r.FormValue("panel_embed_description")),
PanelEmbedColor: embedColor,
}
if err := h.ConvocRepo.Upsert(ctx, cfg); err != nil {
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/convocations?flash=Configuration+sauvegardée", http.StatusSeeOther)
}
// HandleCreatePanel serves POST /convocations/panels/new — creates a new convocation panel entry.
func (h *ConvocationsHandler) HandleCreatePanel(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
if h.ConvocPanelRepo == nil {
http.Redirect(w, r, "/convocations?error=Non+configuré", http.StatusSeeOther)
return
}
color := strings.TrimSpace(r.FormValue("embed_color"))
if color == "" {
color = "#5865f2"
}
p := &db.ConvocationPanel{
GuildID: h.GuildID,
EmbedTitle: strings.TrimSpace(r.FormValue("embed_title")),
EmbedDesc: strings.TrimSpace(r.FormValue("embed_description")),
EmbedColor: color,
ChannelID: strings.TrimSpace(r.FormValue("channel_id")),
}
if err := h.ConvocPanelRepo.Create(ctx, p); err != nil {
http.Redirect(w, r, "/convocations?error="+urlEncode(err.Error()), http.StatusSeeOther)
return
}
http.Redirect(w, r, "/convocations?flash=Panel+créé", http.StatusSeeOther)
}
// HandleDeletePanel serves POST /convocations/panels/{id}/delete.
func (h *ConvocationsHandler) HandleDeletePanel(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
idStr := chi.URLParam(r, "id")
var id int64
fmt.Sscanf(idStr, "%d", &id)
if h.ConvocPanelRepo != nil {
h.ConvocPanelRepo.Delete(ctx, id) //nolint
}
http.Redirect(w, r, "/convocations?flash=Panel+supprimé", http.StatusSeeOther)
}
// HandleSendPanel serves POST /convocations/panels/{id}/send.
func (h *ConvocationsHandler) HandleSendPanel(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
var id int64
fmt.Sscanf(idStr, "%d", &id)
if h.BotService == nil {
http.Redirect(w, r, "/convocations?error=Bot+non+disponible", http.StatusSeeOther)
return
}
if err := h.BotService.SendConvocPanelByID(id); err != nil {
http.Redirect(w, r, "/convocations?error="+urlEncode(err.Error()), http.StatusSeeOther)
return
}
http.Redirect(w, r, "/convocations?flash=Panel+envoyé+sur+Discord", http.StatusSeeOther)
}
// HandleCreate serves POST /convocations/create — creates a convocation via Discord.
func (h *ConvocationsHandler) HandleCreate(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
if h.BotService == nil {
http.Error(w, "bot non disponible", http.StatusServiceUnavailable)
return
}
guildID := strings.TrimSpace(r.FormValue("guild_id"))
if guildID == "" {
guildID = h.GuildID
}
targetID := strings.TrimSpace(r.FormValue("target_id"))
reason := strings.TrimSpace(r.FormValue("reason"))
if targetID == "" || reason == "" {
http.Redirect(w, r, "/convocations?error=ID+utilisateur+et+raison+requis", http.StatusSeeOther)
return
}
admin := panelauth.AdminFromContext(r.Context())
staffDiscordID := ""
if admin != nil {
staffDiscordID = admin.DiscordID
}
channelID, err := h.BotService.CreateConvocation(guildID, staffDiscordID, targetID, reason)
if err != nil {
http.Redirect(w, r, "/convocations?error="+urlEncode(err.Error()), http.StatusSeeOther)
return
}
http.Redirect(w, r, "/convocations?flash=Convocation+créée+dans+<#"+channelID+">", http.StatusSeeOther)
}
func urlEncode(s string) string {
var out strings.Builder
for _, r := range s {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9':
out.WriteRune(r)
case r == '-' || r == '_' || r == '.' || r == '~':
out.WriteRune(r)
default:
out.WriteString("+" + strings.ReplaceAll(string(r), " ", "+"))
}
}
return out.String()
}