xtablo-source/backend/internal/web/handlers_discussion.go
2026-05-16 10:11:14 +02:00

118 lines
4.4 KiB
Go

package web
import (
"log/slog"
"net/http"
"strings"
"backend/internal/db/sqlc"
"backend/templates"
"github.com/google/uuid"
"github.com/gorilla/csrf"
"github.com/jackc/pgx/v5/pgtype"
)
type DiscussionDeps struct {
Queries *sqlc.Queries
}
func loadDiscussionTabData(w http.ResponseWriter, r *http.Request, q *sqlc.Queries, tablo sqlc.Tablo) (templates.DiscussionTabData, bool) {
rows, err := q.ListDiscussionMessagesByTablo(r.Context(), tablo.ID)
if err != nil {
slog.Default().Error("discussion: ListDiscussionMessagesByTablo failed", "tablo_id", tablo.ID, "err", err)
http.Error(w, "internal server error", http.StatusInternalServerError)
return templates.DiscussionTabData{}, false
}
data := templates.DiscussionTabData{Messages: templates.DiscussionMessagesFromRows(rows)}
return data, true
}
func markDiscussionRead(r *http.Request, q *sqlc.Queries, tablo sqlc.Tablo, userID uuid.UUID, data templates.DiscussionTabData) {
if len(data.Messages) == 0 {
return
}
last := data.Messages[len(data.Messages)-1]
if _, err := q.UpsertDiscussionReadState(r.Context(), sqlc.UpsertDiscussionReadStateParams{
TabloID: tablo.ID,
UserID: userID,
LastReadMessageID: pgtype.UUID{Bytes: last.ID, Valid: true},
}); err != nil {
slog.Default().Warn("discussion: UpsertDiscussionReadState failed", "tablo_id", tablo.ID, "err", err)
}
}
func TabloDiscussionTabHandler(deps DiscussionDeps) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tablo, user, ok := loadOwnedTablo(w, r, TablosDeps{Queries: deps.Queries})
if !ok {
return
}
data, ok := loadDiscussionTabData(w, r, deps.Queries, tablo)
if !ok {
return
}
markDiscussionRead(r, deps.Queries, tablo, user.ID, data)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if r.Header.Get("HX-Request") == "true" {
_ = templates.DiscussionTabFragment(tablo, data, templates.DiscussionForm{}, templates.DiscussionErrors{}, csrf.Token(r)).Render(r.Context(), w)
return
}
_ = templates.TabloDetailPage(user, csrf.Token(r), tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, data, "discussion").Render(r.Context(), w)
}
}
func DiscussionMessageCreateHandler(deps DiscussionDeps) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
tablo, user, ok := loadOwnedTablo(w, r, TablosDeps{Queries: deps.Queries})
if !ok {
return
}
body := strings.TrimSpace(r.PostFormValue("body"))
form := templates.DiscussionForm{Body: r.PostFormValue("body")}
var errs templates.DiscussionErrors
if body == "" {
errs.Body = "Message is required."
} else if len([]rune(body)) > templates.DiscussionMaxBodyLength {
errs.Body = "Message is too long."
}
if errs.Body != "" {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusUnprocessableEntity)
_ = templates.DiscussionComposer(tablo, form, errs, csrf.Token(r)).Render(r.Context(), w)
return
}
msg, err := deps.Queries.CreateDiscussionMessage(r.Context(), sqlc.CreateDiscussionMessageParams{
TabloID: tablo.ID,
AuthorUserID: user.ID,
Body: body,
})
if err != nil {
slog.Default().Error("discussion create: CreateDiscussionMessage failed", "tablo_id", tablo.ID, "err", err)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)
errs.General = "Message could not be sent. Please try again."
_ = templates.DiscussionComposer(tablo, form, errs, csrf.Token(r)).Render(r.Context(), w)
return
}
row, err := deps.Queries.GetDiscussionMessageWithAuthor(r.Context(), sqlc.GetDiscussionMessageWithAuthorParams{
ID: msg.ID,
TabloID: tablo.ID,
})
if err != nil {
slog.Default().Error("discussion create: GetDiscussionMessageWithAuthor failed", "tablo_id", tablo.ID, "message_id", msg.ID, "err", err)
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
data := templates.DiscussionTabData{Messages: []templates.DiscussionMessageView{templates.DiscussionMessageFromRow(row)}}
markDiscussionRead(r, deps.Queries, tablo, user.ID, data)
if r.Header.Get("HX-Request") == "true" {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_ = templates.DiscussionMessageRow(templates.DiscussionMessageFromRow(row)).Render(r.Context(), w)
return
}
http.Redirect(w, r, templates.DiscussionURL(tablo.ID), http.StatusSeeOther)
}
}