package handlers import ( "database/sql" "fmt" "net/http" "strconv" "time" "synlotto-website/helpers" "synlotto-website/storage" ) func SyndicateInviteHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID, ok := helpers.GetCurrentUserID(r) if !ok { helpers.RenderError(w, r, http.StatusForbidden) return } switch r.Method { case http.MethodGet: syndicateID := helpers.Atoi(r.URL.Query().Get("id")) data := BuildTemplateData(db, w, r) context := helpers.TemplateContext(w, r, data) context["SyndicateID"] = syndicateID tmpl := helpers.LoadTemplateFiles("invite-syndicate.html", "templates/syndicate/invite.html") err := tmpl.ExecuteTemplate(w, "layout", context) if err != nil { helpers.RenderError(w, r, 500) } case http.MethodPost: syndicateID := helpers.Atoi(r.FormValue("syndicate_id")) username := r.FormValue("username") err := storage.InviteToSyndicate(db, userID, syndicateID, username) if err != nil { helpers.SetFlash(w, r, "Failed to send invite: "+err.Error()) } else { helpers.SetFlash(w, r, "Invite sent successfully.") } http.Redirect(w, r, "/syndicate/view?id="+strconv.Itoa(syndicateID), http.StatusSeeOther) default: helpers.RenderError(w, r, http.StatusMethodNotAllowed) } } } func ViewInvitesHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID, ok := helpers.GetCurrentUserID(r) if !ok { helpers.RenderError(w, r, 403) return } invites := storage.GetPendingInvites(db, userID) data := BuildTemplateData(db, w, r) context := helpers.TemplateContext(w, r, data) context["Invites"] = invites tmpl := helpers.LoadTemplateFiles("invites.html", "templates/syndicate/invites.html") tmpl.ExecuteTemplate(w, "layout", context) } } func AcceptInviteHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { inviteID := helpers.Atoi(r.URL.Query().Get("id")) userID, ok := helpers.GetCurrentUserID(r) if !ok { helpers.RenderError(w, r, 403) return } err := storage.AcceptInvite(db, inviteID, userID) if err != nil { helpers.SetFlash(w, r, "Failed to accept invite") } else { helpers.SetFlash(w, r, "You have joined the syndicate") } http.Redirect(w, r, "/syndicate", http.StatusSeeOther) } } func DeclineInviteHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { inviteID := helpers.Atoi(r.URL.Query().Get("id")) _ = storage.UpdateInviteStatus(db, inviteID, "declined") http.Redirect(w, r, "/syndicate/invites", http.StatusSeeOther) } } func CreateInviteToken(db *sql.DB, syndicateID, invitedByID int, ttlHours int) (string, error) { token, err := helpers.GenerateSecureToken() if err != nil { return "", err } expires := time.Now().Add(time.Duration(ttlHours) * time.Hour) _, err = db.Exec(` INSERT INTO syndicate_invite_tokens (syndicate_id, token, invited_by_user_id, expires_at) VALUES (?, ?, ?, ?) `, syndicateID, token, invitedByID, expires) return token, err } func AcceptInviteToken(db *sql.DB, 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 { return fmt.Errorf("invalid or expired token") } if acceptedAt.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 { return err } _, err = 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 { return func(w http.ResponseWriter, r *http.Request) { userID, ok := helpers.GetCurrentUserID(r) if !ok { helpers.RenderError(w, r, http.StatusForbidden) return } syndicateID := helpers.Atoi(r.URL.Query().Get("id")) token, err := CreateInviteToken(db, syndicateID, userID, 48) // token valid for 48 hours if err != nil { helpers.SetFlash(w, r, "Failed to generate invite link.") http.Redirect(w, r, "/syndicate/view?id="+strconv.Itoa(syndicateID), http.StatusSeeOther) return } origin := r.Host if r.TLS != nil { origin = "https://" + origin } else { origin = "http://" + origin } inviteLink := fmt.Sprintf("%s/syndicate/join?token=%s", origin, token) helpers.SetFlash(w, r, "Invite link created: "+inviteLink) http.Redirect(w, r, "/syndicate/view?id="+strconv.Itoa(syndicateID), http.StatusSeeOther) } } func JoinSyndicateWithTokenHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID, ok := helpers.GetCurrentUserID(r) if !ok { helpers.RenderError(w, r, http.StatusForbidden) return } token := r.URL.Query().Get("token") if token == "" { helpers.SetFlash(w, r, "Invalid or missing invite token.") http.Redirect(w, r, "/syndicate", http.StatusSeeOther) return } err := AcceptInviteToken(db, token, userID) if err != nil { helpers.SetFlash(w, r, "Failed to join syndicate: "+err.Error()) } else { helpers.SetFlash(w, r, "You have joined the syndicate!") } http.Redirect(w, r, "/syndicate", http.StatusSeeOther) } } func ManageInviteTokensHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID, ok := helpers.GetCurrentUserID(r) if !ok { helpers.RenderError(w, r, 403) return } syndicateID := helpers.Atoi(r.URL.Query().Get("id")) if !storage.IsSyndicateManager(db, syndicateID, userID) { helpers.RenderError(w, r, 403) return } tokens := storage.GetInviteTokensForSyndicate(db, syndicateID) data := BuildTemplateData(db, w, r) context := helpers.TemplateContext(w, r, data) context["Tokens"] = tokens context["SyndicateID"] = syndicateID tmpl := helpers.LoadTemplateFiles("invite-links.html", "templates/syndicate/invite_links.html") tmpl.ExecuteTemplate(w, "layout", context) } }