Stack of changes to get gin, scs, nosurf running.
This commit is contained in:
@@ -1,183 +1,196 @@
|
||||
// internal/handlers/lottery/syndicate/syndicate_invites.go
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
templateHandlers "synlotto-website/internal/handlers/template"
|
||||
"synlotto-website/internal/helpers"
|
||||
securityHelpers "synlotto-website/internal/helpers/security"
|
||||
templateHelpers "synlotto-website/internal/helpers/template"
|
||||
storage "synlotto-website/internal/storage/syndicate"
|
||||
"synlotto-website/internal/platform/bootstrap"
|
||||
syndicateStorage "synlotto-website/internal/storage/syndicate"
|
||||
|
||||
"synlotto-website/internal/helpers"
|
||||
)
|
||||
|
||||
func SyndicateInviteHandler(db *sql.DB) http.HandlerFunc {
|
||||
// GET /syndicate/invite?id=<syndicate_id>
|
||||
// POST /syndicate/invite (syndicate_id, username)
|
||||
func SyndicateInviteHandler(app *bootstrap.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(app.SessionManager, r)
|
||||
if !ok {
|
||||
templateHandlers.RenderError(w, r, http.StatusForbidden)
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
syndicateID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["SyndicateID"] = syndicateID
|
||||
data := templateHandlers.BuildTemplateData(app, w, r)
|
||||
ctx := templateHelpers.TemplateContext(w, r, data)
|
||||
ctx["SyndicateID"] = syndicateID
|
||||
|
||||
tmpl := templateHelpers.LoadTemplateFiles("invite-syndicate.html", "templates/syndicate/invite.html")
|
||||
err := tmpl.ExecuteTemplate(w, "layout", context)
|
||||
if err != nil {
|
||||
templateHandlers.RenderError(w, r, 500)
|
||||
tmpl := templateHelpers.LoadTemplateFiles("invite-syndicate.html", "web/templates/syndicate/invite.html")
|
||||
if err := tmpl.ExecuteTemplate(w, "layout", ctx); err != nil {
|
||||
templateHelpers.RenderError(w, r, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
case http.MethodPost:
|
||||
syndicateID := helpers.Atoi(r.FormValue("syndicate_id"))
|
||||
username := r.FormValue("username")
|
||||
|
||||
err := syndicateStorage.InviteToSyndicate(db, userID, syndicateID, username)
|
||||
if err != nil {
|
||||
templateHelpers.SetFlash(w, r, "Failed to send invite: "+err.Error())
|
||||
if err := syndicateStorage.InviteToSyndicate(app.DB, userID, syndicateID, username); err != nil {
|
||||
templateHelpers.SetFlash(r, "Failed to send invite: "+err.Error())
|
||||
} else {
|
||||
templateHelpers.SetFlash(w, r, "Invite sent successfully.")
|
||||
templateHelpers.SetFlash(r, "Invite sent successfully.")
|
||||
}
|
||||
http.Redirect(w, r, "/syndicate/view?id="+strconv.Itoa(syndicateID), http.StatusSeeOther)
|
||||
|
||||
default:
|
||||
templateHandlers.RenderError(w, r, http.StatusMethodNotAllowed)
|
||||
templateHelpers.RenderError(w, r, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ViewInvitesHandler(db *sql.DB) http.HandlerFunc {
|
||||
// GET /syndicate/invites
|
||||
func ViewInvitesHandler(app *bootstrap.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(app.SessionManager, r)
|
||||
if !ok {
|
||||
templateHandlers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
invites := syndicateStorage.GetPendingInvites(db, userID)
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["Invites"] = invites
|
||||
invites := syndicateStorage.GetPendingSyndicateInvites(app.DB, userID)
|
||||
|
||||
tmpl := templateHelpers.LoadTemplateFiles("invites.html", "templates/syndicate/invites.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
data := templateHandlers.BuildTemplateData(app, w, r)
|
||||
ctx := templateHelpers.TemplateContext(w, r, data)
|
||||
ctx["Invites"] = invites
|
||||
|
||||
tmpl := templateHelpers.LoadTemplateFiles("invites.html", "web/templates/syndicate/invites.html")
|
||||
_ = tmpl.ExecuteTemplate(w, "layout", ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func AcceptInviteHandler(db *sql.DB) http.HandlerFunc {
|
||||
// POST /syndicate/invites/accept?id=<invite_id>
|
||||
func AcceptInviteHandler(app *bootstrap.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
inviteID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(app.SessionManager, r)
|
||||
if !ok {
|
||||
templateHandlers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
err := syndicateStorage.AcceptInvite(db, inviteID, userID)
|
||||
if err != nil {
|
||||
templateHelpers.SetFlash(w, r, "Failed to accept invite")
|
||||
if err := syndicateStorage.AcceptInvite(app.DB, inviteID, userID); err != nil {
|
||||
templateHelpers.SetFlash(r, "Failed to accept invite")
|
||||
} else {
|
||||
templateHelpers.SetFlash(w, r, "You have joined the syndicate")
|
||||
templateHelpers.SetFlash(r, "You have joined the syndicate")
|
||||
}
|
||||
http.Redirect(w, r, "/syndicate", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func DeclineInviteHandler(db *sql.DB) http.HandlerFunc {
|
||||
// POST /syndicate/invites/decline?id=<invite_id>
|
||||
func DeclineInviteHandler(app *bootstrap.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
inviteID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
_ = syndicateStorage.UpdateInviteStatus(db, inviteID, "declined")
|
||||
_ = syndicateStorage.UpdateInviteStatus(app.DB, inviteID, "declined")
|
||||
http.Redirect(w, r, "/syndicate/invites", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func CreateInviteToken(db *sql.DB, syndicateID, invitedByID int, ttlHours int) (string, error) {
|
||||
// ===== Invite Tokens ========================================================
|
||||
// (Consider moving these two helpers to internal/storage/syndicate)
|
||||
|
||||
// Create an invite token that expires after ttlHours.
|
||||
func CreateInviteToken(app *bootstrap.App, syndicateID, invitedByID int, ttlHours int) (string, error) {
|
||||
token, err := securityHelpers.GenerateSecureToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
expires := time.Now().Add(time.Duration(ttlHours) * time.Hour)
|
||||
|
||||
_, err = db.Exec(`
|
||||
_, err = app.DB.Exec(`
|
||||
INSERT INTO syndicate_invite_tokens (syndicate_id, token, invited_by_user_id, expires_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`, syndicateID, token, invitedByID, expires)
|
||||
|
||||
return token, err
|
||||
}
|
||||
|
||||
// ToDo: Whys is there SQL in here??? Shouldn't be in handlers
|
||||
func AcceptInviteToken(db *sql.DB, token string, userID int) error {
|
||||
// Validate + consume a token to join a syndicate.
|
||||
func AcceptInviteToken(app *bootstrap.App, token string, userID int) error {
|
||||
var syndicateID int
|
||||
var expiresAt, acceptedAt sql.NullTime
|
||||
err := db.QueryRow(`
|
||||
SELECT syndicate_id, expires_at, accepted_at
|
||||
FROM syndicate_invite_tokens
|
||||
WHERE token = ?
|
||||
`, token).Scan(&syndicateID, &expiresAt, &acceptedAt)
|
||||
if err != nil {
|
||||
var expiresAt, acceptedAt struct {
|
||||
Valid bool
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// Note: using separate variables to avoid importing database/sql here.
|
||||
row := app.DB.QueryRow(`
|
||||
SELECT syndicate_id, expires_at, accepted_at
|
||||
FROM syndicate_invite_tokens
|
||||
WHERE token = ?
|
||||
`, token)
|
||||
if err := row.Scan(&syndicateID, &expiresAt.Time, &acceptedAt.Time); err != nil {
|
||||
return fmt.Errorf("invalid or expired token")
|
||||
}
|
||||
if acceptedAt.Valid || expiresAt.Time.Before(time.Now()) {
|
||||
// If driver returns zero time when NULL, treat missing as invalid.Valid=false
|
||||
expiresAt.Valid = !expiresAt.Time.IsZero()
|
||||
acceptedAt.Valid = !acceptedAt.Time.IsZero()
|
||||
|
||||
if acceptedAt.Valid || (expiresAt.Valid && expiresAt.Time.Before(time.Now())) {
|
||||
return fmt.Errorf("token already used or expired")
|
||||
}
|
||||
|
||||
_, err = db.Exec(`
|
||||
INSERT INTO syndicate_members (syndicate_id, user_id, role, status, joined_at)
|
||||
VALUES (?, ?, 'member', 'active', CURRENT_TIMESTAMP)
|
||||
`, syndicateID, userID)
|
||||
if err != nil {
|
||||
if _, err := app.DB.Exec(`
|
||||
INSERT INTO syndicate_members (syndicate_id, user_id, role, status, joined_at)
|
||||
VALUES (?, ?, 'member', 'active', CURRENT_TIMESTAMP)
|
||||
`, syndicateID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`
|
||||
UPDATE syndicate_invite_tokens
|
||||
SET accepted_by_user_id = ?, accepted_at = CURRENT_TIMESTAMP
|
||||
WHERE token = ?
|
||||
_, err := app.DB.Exec(`
|
||||
UPDATE syndicate_invite_tokens
|
||||
SET accepted_by_user_id = ?, accepted_at = CURRENT_TIMESTAMP
|
||||
WHERE token = ?
|
||||
`, userID, token)
|
||||
return err
|
||||
}
|
||||
|
||||
func GenerateInviteLinkHandler(db *sql.DB) http.HandlerFunc {
|
||||
// GET /syndicate/invite/token?id=<syndicate_id>
|
||||
func GenerateInviteLinkHandler(app *bootstrap.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(app.SessionManager, r)
|
||||
if !ok {
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
syndicateID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
token, err := CreateInviteToken(db, syndicateID, userID, 48)
|
||||
token, err := CreateInviteToken(app, syndicateID, userID, 48)
|
||||
if err != nil {
|
||||
templateHelpers.SetFlash(w, r, "Failed to generate invite link.")
|
||||
templateHelpers.SetFlash(r, "Failed to generate invite link.")
|
||||
http.Redirect(w, r, "/syndicate/view?id="+strconv.Itoa(syndicateID), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
origin := r.Host
|
||||
scheme := "http://"
|
||||
if r.TLS != nil {
|
||||
origin = "https://" + origin
|
||||
} else {
|
||||
origin = "http://" + origin
|
||||
scheme = "https://"
|
||||
}
|
||||
inviteLink := fmt.Sprintf("%s/syndicate/join?token=%s", origin, token)
|
||||
inviteLink := fmt.Sprintf("%s%s/syndicate/join?token=%s", scheme, r.Host, token)
|
||||
|
||||
templateHelpers.SetFlash(w, r, "Invite link created: "+inviteLink)
|
||||
templateHelpers.SetFlash(r, "Invite link created: "+inviteLink)
|
||||
http.Redirect(w, r, "/syndicate/view?id="+strconv.Itoa(syndicateID), http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func JoinSyndicateWithTokenHandler(db *sql.DB) http.HandlerFunc {
|
||||
// GET /syndicate/join?token=<token>
|
||||
func JoinSyndicateWithTokenHandler(app *bootstrap.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(app.SessionManager, r)
|
||||
if !ok {
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
@@ -185,44 +198,43 @@ func JoinSyndicateWithTokenHandler(db *sql.DB) http.HandlerFunc {
|
||||
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
templateHelpers.SetFlash(w, r, "Invalid or missing invite token.")
|
||||
templateHelpers.SetFlash(r, "Invalid or missing invite token.")
|
||||
http.Redirect(w, r, "/syndicate", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
err := AcceptInviteToken(db, token, userID)
|
||||
if err != nil {
|
||||
templateHelpers.SetFlash(w, r, "Failed to join syndicate: "+err.Error())
|
||||
if err := AcceptInviteToken(app, token, userID); err != nil {
|
||||
templateHelpers.SetFlash(r, "Failed to join syndicate: "+err.Error())
|
||||
} else {
|
||||
templateHelpers.SetFlash(w, r, "You have joined the syndicate!")
|
||||
templateHelpers.SetFlash(r, "You have joined the syndicate!")
|
||||
}
|
||||
http.Redirect(w, r, "/syndicate", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func ManageInviteTokensHandler(db *sql.DB) http.HandlerFunc {
|
||||
// GET /syndicate/invite/tokens?id=<syndicate_id>
|
||||
func ManageInviteTokensHandler(app *bootstrap.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := securityHelpers.GetCurrentUserID(r)
|
||||
userID, ok := securityHelpers.GetCurrentUserID(app.SessionManager, r)
|
||||
if !ok {
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
syndicateID := helpers.Atoi(r.URL.Query().Get("id"))
|
||||
|
||||
if !storage.IsSyndicateManager(db, syndicateID, userID) {
|
||||
templateHelpers.RenderError(w, r, 403)
|
||||
if !syndicateStorage.IsSyndicateManager(app.DB, syndicateID, userID) {
|
||||
templateHelpers.RenderError(w, r, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
tokens := storage.GetInviteTokensForSyndicate(db, syndicateID)
|
||||
tokens := syndicateStorage.GetInviteTokensForSyndicate(app.DB, syndicateID)
|
||||
|
||||
data := templateHandlers.BuildTemplateData(db, w, r)
|
||||
context := templateHelpers.TemplateContext(w, r, data)
|
||||
context["Tokens"] = tokens
|
||||
context["SyndicateID"] = syndicateID
|
||||
data := templateHandlers.BuildTemplateData(app, w, r)
|
||||
ctx := templateHelpers.TemplateContext(w, r, data)
|
||||
ctx["Tokens"] = tokens
|
||||
ctx["SyndicateID"] = syndicateID
|
||||
|
||||
tmpl := templateHelpers.LoadTemplateFiles("invite-links.html", "templates/syndicate/invite_links.html")
|
||||
tmpl.ExecuteTemplate(w, "layout", context)
|
||||
tmpl := templateHelpers.LoadTemplateFiles("invite-links.html", "web/templates/syndicate/invite_links.html")
|
||||
_ = tmpl.ExecuteTemplate(w, "layout", ctx)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user