Been too long since i did anything, can't remember what the hell is in all this....
This commit is contained in:
@@ -25,7 +25,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
|
|||||||
err := tmpl.ExecuteTemplate(w, "layout", context)
|
err := tmpl.ExecuteTemplate(w, "layout", context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("❌ Template render error:", err)
|
log.Println("❌ Template render error:", err)
|
||||||
http.Error(w, "Error rendering login page", http.StatusInternalServerError)
|
http.Error(w, "Error rendering login page", http.StatusInternalServerError) // Take hte flash message from licnse server this just does a black page also should be using db ahain see licvense server
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func AdminAccessLogHandler(db *sql.DB) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var logs []AdminLogEntry
|
var logs []AdminLogEntry // ToDo should be in models
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var entry AdminLogEntry
|
var entry AdminLogEntry
|
||||||
if err := rows.Scan(&entry.AccessedAt, &entry.UserID, &entry.Path, &entry.IP, &entry.UserAgent); err != nil {
|
if err := rows.Scan(&entry.AccessedAt, &entry.UserID, &entry.Path, &entry.IP, &entry.UserAgent); err != nil {
|
||||||
|
|||||||
@@ -193,3 +193,30 @@ func JoinSyndicateWithTokenHandler(db *sql.DB) http.HandlerFunc {
|
|||||||
http.Redirect(w, r, "/syndicate", http.StatusSeeOther)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func BuildTemplateData(db *sql.DB, w http.ResponseWriter, r *http.Request) model
|
|||||||
|
|
||||||
switch v := session.Values["user_id"].(type) {
|
switch v := session.Values["user_id"].(type) {
|
||||||
case int:
|
case int:
|
||||||
user = models.GetUserByID(v)
|
user = models.GetUserByID(v) // ToDo should be storage not models
|
||||||
case int64:
|
case int64:
|
||||||
user = models.GetUserByID(int(v))
|
user = models.GetUserByID(int(v))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func RenderError(w http.ResponseWriter, r *http.Request, statusCode int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("✅ Successfully rendered 500 page")
|
log.Println("✅ Successfully rendered error page") // ToDo: log these to database
|
||||||
}
|
}
|
||||||
|
|
||||||
//ToDo Pages.go /template.go to be merged?
|
//ToDo Pages.go /template.go to be merged?
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func GetVisitorLimiter(ip string) *rate.Limiter {
|
|||||||
|
|
||||||
limiter, exists := visitors[ip]
|
limiter, exists := visitors[ip]
|
||||||
if !exists {
|
if !exists {
|
||||||
limiter = rate.NewLimiter(1, 5)
|
limiter = rate.NewLimiter(3, 5)
|
||||||
visitors[ip] = limiter
|
visitors[ip] = limiter
|
||||||
}
|
}
|
||||||
return limiter
|
return limiter
|
||||||
|
|||||||
66
main.go
66
main.go
@@ -1,14 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"synlotto-website/handlers"
|
"synlotto-website/handlers"
|
||||||
admin "synlotto-website/handlers/admin"
|
|
||||||
"synlotto-website/helpers"
|
"synlotto-website/helpers"
|
||||||
"synlotto-website/middleware"
|
"synlotto-website/middleware"
|
||||||
"synlotto-website/models"
|
"synlotto-website/models"
|
||||||
|
"synlotto-website/routes"
|
||||||
"synlotto-website/storage"
|
"synlotto-website/storage"
|
||||||
|
|
||||||
"github.com/gorilla/csrf"
|
"github.com/gorilla/csrf"
|
||||||
@@ -16,7 +15,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
db := storage.InitDB("synlotto.db")
|
db := storage.InitDB("synlotto.db")
|
||||||
models.SetDB(db)
|
models.SetDB(db) // Should be in storage not models.
|
||||||
|
|
||||||
var isProduction = false
|
var isProduction = false
|
||||||
|
|
||||||
@@ -27,13 +26,12 @@ func main() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
setupAdminRoutes(mux, db)
|
routes.SetupAdminRoutes(mux, db)
|
||||||
setupAccountRoutes(mux, db)
|
routes.SetupAccountRoutes(mux, db)
|
||||||
setupResultRoutes(mux, db)
|
routes.SetupResultRoutes(mux, db)
|
||||||
setupSyndicateRoutes(mux, db)
|
routes.SetupSyndicateRoutes(mux, db)
|
||||||
|
|
||||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||||
|
|
||||||
mux.HandleFunc("/", handlers.Home(db))
|
mux.HandleFunc("/", handlers.Home(db))
|
||||||
|
|
||||||
wrapped := helpers.RateLimit(csrfMiddleware(mux))
|
wrapped := helpers.RateLimit(csrfMiddleware(mux))
|
||||||
@@ -44,55 +42,3 @@ func main() {
|
|||||||
log.Println("🌐 Running on http://localhost:8080")
|
log.Println("🌐 Running on http://localhost:8080")
|
||||||
http.ListenAndServe(":8080", wrapped)
|
http.ListenAndServe(":8080", wrapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupAdminRoutes(mux *http.ServeMux, db *sql.DB) {
|
|
||||||
mux.HandleFunc("/admin/access", middleware.AdminOnly(db, admin.AdminAccessLogHandler(db)))
|
|
||||||
mux.HandleFunc("/admin/audit", middleware.AdminOnly(db, admin.AuditLogHandler(db)))
|
|
||||||
mux.HandleFunc("/admin/dashboard", middleware.AdminOnly(db, admin.AdminDashboardHandler(db)))
|
|
||||||
mux.HandleFunc("/admin/triggers", middleware.AdminOnly(db, admin.AdminTriggersHandler(db)))
|
|
||||||
|
|
||||||
// Draw management
|
|
||||||
mux.HandleFunc("/admin/draws", middleware.AdminOnly(db, admin.ListDrawsHandler(db)))
|
|
||||||
// mux.HandleFunc("/admin/draws/new", middleware.AdminOnly(db, admin.RenderNewDrawForm(db)))
|
|
||||||
// mux.HandleFunc("/admin/draws/submit", middleware.AdminOnly(db, admin.CreateDrawHandler(db)))
|
|
||||||
mux.HandleFunc("/admin/draws/modify", middleware.AdminOnly(db, admin.ModifyDrawHandler(db)))
|
|
||||||
mux.HandleFunc("/admin/draws/delete", middleware.AdminOnly(db, admin.DeleteDrawHandler(db)))
|
|
||||||
|
|
||||||
// Prize management
|
|
||||||
mux.HandleFunc("/admin/draws/prizes/add", middleware.AdminOnly(db, admin.AddPrizesHandler(db)))
|
|
||||||
mux.HandleFunc("/admin/draws/prizes/modify", middleware.AdminOnly(db, admin.ModifyPrizesHandler(db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupAccountRoutes(mux *http.ServeMux, db *sql.DB) {
|
|
||||||
mux.HandleFunc("/login", middleware.Auth(false)(handlers.Login))
|
|
||||||
mux.HandleFunc("/logout", handlers.Logout)
|
|
||||||
mux.HandleFunc("/signup", middleware.Auth(false)(handlers.Signup))
|
|
||||||
mux.HandleFunc("/account/tickets/add_ticket", handlers.AddTicket(db))
|
|
||||||
mux.HandleFunc("/account/tickets/my_tickets", handlers.GetMyTickets(db))
|
|
||||||
mux.HandleFunc("/account/messages", middleware.Auth(true)(handlers.MessagesInboxHandler(db)))
|
|
||||||
mux.HandleFunc("/account/messages/read", middleware.Auth(true)(handlers.ReadMessageHandler(db)))
|
|
||||||
mux.HandleFunc("/account/messages/archive", middleware.Auth(true)(handlers.ArchiveMessageHandler(db)))
|
|
||||||
mux.HandleFunc("/account/messages/archived", middleware.Auth(true)(handlers.ArchivedMessagesHandler(db)))
|
|
||||||
mux.HandleFunc("/account/messages/restore", middleware.Auth(true)(handlers.RestoreMessageHandler(db)))
|
|
||||||
mux.HandleFunc("/account/messages/send", middleware.Auth(true)(handlers.SendMessageHandler(db)))
|
|
||||||
mux.HandleFunc("/account/notifications", middleware.Auth(true)(handlers.NotificationsHandler(db)))
|
|
||||||
mux.HandleFunc("/account/notifications/read", middleware.Auth(true)(handlers.MarkNotificationReadHandler(db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupResultRoutes(mux *http.ServeMux, db *sql.DB) {
|
|
||||||
mux.HandleFunc("/results/thunderball", handlers.ResultsThunderball(db))
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupSyndicateRoutes(mux *http.ServeMux, db *sql.DB) {
|
|
||||||
mux.HandleFunc("/syndicate", middleware.Auth(true)(handlers.ListSyndicatesHandler(db)))
|
|
||||||
mux.HandleFunc("/syndicate/create", middleware.Auth(true)(handlers.CreateSyndicateHandler(db)))
|
|
||||||
mux.HandleFunc("/syndicate/view", middleware.Auth(true)(handlers.ViewSyndicateHandler(db)))
|
|
||||||
mux.HandleFunc("/syndicate/tickets", middleware.Auth(true)(handlers.SyndicateTicketsHandler(db)))
|
|
||||||
mux.HandleFunc("/syndicate/tickets/new", middleware.Auth(true)(handlers.SyndicateLogTicketHandler(db)))
|
|
||||||
mux.HandleFunc("/syndicate/invites", middleware.Auth(true)(handlers.ViewInvitesHandler(db)))
|
|
||||||
mux.HandleFunc("/syndicate/invites/accept", middleware.Auth(true)(handlers.AcceptInviteHandler(db)))
|
|
||||||
mux.HandleFunc("/syndicate/invites/decline", middleware.Auth(true)(handlers.DeclineInviteHandler(db)))
|
|
||||||
mux.HandleFunc("/syndicate/invite/token", middleware.Auth(true)(handlers.GenerateInviteLinkHandler(db)))
|
|
||||||
mux.HandleFunc("/syndicate/join", middleware.Auth(true)(handlers.JoinSyndicateWithTokenHandler(db)))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ func Recover(next http.Handler) http.Handler {
|
|||||||
if rec := recover(); rec != nil {
|
if rec := recover(); rec != nil {
|
||||||
log.Printf("🔥 Recovered from panic: %v\n%s", rec, debug.Stack())
|
log.Printf("🔥 Recovered from panic: %v\n%s", rec, debug.Stack())
|
||||||
|
|
||||||
// ✅ Call your custom template-based fallback
|
|
||||||
helpers.RenderError(w, r, http.StatusInternalServerError)
|
helpers.RenderError(w, r, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package middleware
|
|||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
// Redirects all HTTP to HTTPS (only in production)
|
|
||||||
func EnforceHTTPS(next http.Handler, enabled bool) http.Handler {
|
func EnforceHTTPS(next http.Handler, enabled bool) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if enabled && r.Header.Get("X-Forwarded-Proto") != "https" && r.TLS == nil {
|
if enabled && r.Header.Get("X-Forwarded-Proto") != "https" && r.TLS == nil {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Syndicate struct {
|
type Syndicate struct {
|
||||||
ID int
|
ID int
|
||||||
@@ -27,3 +30,12 @@ type SyndicateInvite struct {
|
|||||||
Status string
|
Status string
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SyndicateInviteToken struct {
|
||||||
|
Token string
|
||||||
|
InvitedByUserID int
|
||||||
|
AcceptedByUserID sql.NullInt64
|
||||||
|
CreatedAt time.Time
|
||||||
|
ExpiresAt time.Time
|
||||||
|
AcceptedAt sql.NullTime
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func SetDB(database *sql.DB) {
|
|||||||
|
|
||||||
func CreateUser(username, passwordHash string) error {
|
func CreateUser(username, passwordHash string) error {
|
||||||
_, err := db.Exec("INSERT INTO users (username, password_hash) VALUES (?, ?)", username, passwordHash)
|
_, err := db.Exec("INSERT INTO users (username, password_hash) VALUES (?, ?)", username, passwordHash)
|
||||||
|
//ToDo: Why is SQL in here?
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,11 +77,11 @@ func GetUserByID(id int) *User {
|
|||||||
|
|
||||||
func LogLoginAttempt(username string, success bool) {
|
func LogLoginAttempt(username string, success bool) {
|
||||||
_, err := db.Exec("INSERT INTO auditlog (username, success, timestamp) VALUES (?, ?, ?)",
|
_, err := db.Exec("INSERT INTO auditlog (username, success, timestamp) VALUES (?, ?, ?)",
|
||||||
username, boolToInt(success), time.Now().Format(time.RFC3339))
|
username, boolToInt(success), time.Now().Format(time.RFC3339)) // tOdO: SHOULD BE USING UTC
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("❌ Failed to log login:", err)
|
log.Println("❌ Failed to log login:", err)
|
||||||
}
|
}
|
||||||
}
|
} // ToDo this shouldn't be in models. Also why did i build a bool to int? just use the bool
|
||||||
|
|
||||||
func boolToInt(b bool) int {
|
func boolToInt(b bool) int {
|
||||||
if b {
|
if b {
|
||||||
|
|||||||
25
routes/accountroutes.go
Normal file
25
routes/accountroutes.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"synlotto-website/handlers"
|
||||||
|
"synlotto-website/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupAccountRoutes(mux *http.ServeMux, db *sql.DB) {
|
||||||
|
mux.HandleFunc("/login", middleware.Auth(false)(handlers.Login))
|
||||||
|
mux.HandleFunc("/logout", handlers.Logout)
|
||||||
|
mux.HandleFunc("/signup", middleware.Auth(false)(handlers.Signup))
|
||||||
|
mux.HandleFunc("/account/tickets/add_ticket", handlers.AddTicket(db))
|
||||||
|
mux.HandleFunc("/account/tickets/my_tickets", handlers.GetMyTickets(db))
|
||||||
|
mux.HandleFunc("/account/messages", middleware.Auth(true)(handlers.MessagesInboxHandler(db)))
|
||||||
|
mux.HandleFunc("/account/messages/read", middleware.Auth(true)(handlers.ReadMessageHandler(db)))
|
||||||
|
mux.HandleFunc("/account/messages/archive", middleware.Auth(true)(handlers.ArchiveMessageHandler(db)))
|
||||||
|
mux.HandleFunc("/account/messages/archived", middleware.Auth(true)(handlers.ArchivedMessagesHandler(db)))
|
||||||
|
mux.HandleFunc("/account/messages/restore", middleware.Auth(true)(handlers.RestoreMessageHandler(db)))
|
||||||
|
mux.HandleFunc("/account/messages/send", middleware.Auth(true)(handlers.SendMessageHandler(db)))
|
||||||
|
mux.HandleFunc("/account/notifications", middleware.Auth(true)(handlers.NotificationsHandler(db)))
|
||||||
|
mux.HandleFunc("/account/notifications/read", middleware.Auth(true)(handlers.MarkNotificationReadHandler(db)))
|
||||||
|
}
|
||||||
27
routes/adminroutes.go
Normal file
27
routes/adminroutes.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
admin "synlotto-website/handlers/admin"
|
||||||
|
"synlotto-website/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupAdminRoutes(mux *http.ServeMux, db *sql.DB) {
|
||||||
|
mux.HandleFunc("/admin/access", middleware.AdminOnly(db, admin.AdminAccessLogHandler(db)))
|
||||||
|
mux.HandleFunc("/admin/audit", middleware.AdminOnly(db, admin.AuditLogHandler(db)))
|
||||||
|
mux.HandleFunc("/admin/dashboard", middleware.AdminOnly(db, admin.AdminDashboardHandler(db)))
|
||||||
|
mux.HandleFunc("/admin/triggers", middleware.AdminOnly(db, admin.AdminTriggersHandler(db)))
|
||||||
|
|
||||||
|
// Draw management
|
||||||
|
mux.HandleFunc("/admin/draws", middleware.AdminOnly(db, admin.ListDrawsHandler(db)))
|
||||||
|
// mux.HandleFunc("/admin/draws/new", middleware.AdminOnly(db, admin.RenderNewDrawForm(db)))
|
||||||
|
// mux.HandleFunc("/admin/draws/submit", middleware.AdminOnly(db, admin.CreateDrawHandler(db)))
|
||||||
|
mux.HandleFunc("/admin/draws/modify", middleware.AdminOnly(db, admin.ModifyDrawHandler(db)))
|
||||||
|
mux.HandleFunc("/admin/draws/delete", middleware.AdminOnly(db, admin.DeleteDrawHandler(db)))
|
||||||
|
|
||||||
|
// Prize management
|
||||||
|
mux.HandleFunc("/admin/draws/prizes/add", middleware.AdminOnly(db, admin.AddPrizesHandler(db)))
|
||||||
|
mux.HandleFunc("/admin/draws/prizes/modify", middleware.AdminOnly(db, admin.ModifyPrizesHandler(db)))
|
||||||
|
}
|
||||||
12
routes/resultroutes.go
Normal file
12
routes/resultroutes.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"synlotto-website/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupResultRoutes(mux *http.ServeMux, db *sql.DB) {
|
||||||
|
mux.HandleFunc("/results/thunderball", handlers.ResultsThunderball(db))
|
||||||
|
}
|
||||||
24
routes/syndicateroutes.go
Normal file
24
routes/syndicateroutes.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"synlotto-website/handlers"
|
||||||
|
"synlotto-website/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupSyndicateRoutes(mux *http.ServeMux, db *sql.DB) {
|
||||||
|
mux.HandleFunc("/syndicate", middleware.Auth(true)(handlers.ListSyndicatesHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/create", middleware.Auth(true)(handlers.CreateSyndicateHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/view", middleware.Auth(true)(handlers.ViewSyndicateHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/tickets", middleware.Auth(true)(handlers.SyndicateTicketsHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/tickets/new", middleware.Auth(true)(handlers.SyndicateLogTicketHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/invites", middleware.Auth(true)(handlers.ViewInvitesHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/invites/accept", middleware.Auth(true)(handlers.AcceptInviteHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/invites/decline", middleware.Auth(true)(handlers.DeclineInviteHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/invite/token", middleware.Auth(true)(handlers.GenerateInviteLinkHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/invite/tokens", middleware.Auth(true)(handlers.ManageInviteTokensHandler(db)))
|
||||||
|
mux.HandleFunc("/syndicate/join", middleware.Auth(true)(handlers.JoinSyndicateWithTokenHandler(db)))
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ func InitDB(filepath string) *sql.DB {
|
|||||||
SchemaSyndicates,
|
SchemaSyndicates,
|
||||||
SchemaSyndicateMembers,
|
SchemaSyndicateMembers,
|
||||||
SchemaSyndicateInvites,
|
SchemaSyndicateInvites,
|
||||||
|
SchemaSyndicateInviteTokens,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, stmt := range schemas {
|
for _, stmt := range schemas {
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ func GetSyndicateByID(db *sql.DB, id int) (*models.Syndicate, error) {
|
|||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsSyndicateManager(db *sql.DB, syndicateID, userID int) bool {
|
||||||
|
var count int
|
||||||
|
err := db.QueryRow(`
|
||||||
|
SELECT COUNT(*) FROM syndicates
|
||||||
|
WHERE id = ? AND owner_id = ?
|
||||||
|
`, syndicateID, userID).Scan(&count)
|
||||||
|
return err == nil && count > 0
|
||||||
|
}
|
||||||
|
|
||||||
func GetSyndicateMembers(db *sql.DB, syndicateID int) []models.SyndicateMember {
|
func GetSyndicateMembers(db *sql.DB, syndicateID int) []models.SyndicateMember {
|
||||||
rows, err := db.Query(`
|
rows, err := db.Query(`
|
||||||
SELECT m.user_id, u.username, m.joined_at
|
SELECT m.user_id, u.username, m.joined_at
|
||||||
@@ -90,7 +99,7 @@ func IsSyndicateMember(db *sql.DB, syndicateID, userID int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetUserByUsername(db *sql.DB, username string) *models.User {
|
func GetUserByUsername(db *sql.DB, username string) *models.User {
|
||||||
row := db.QueryRow(`SELECT id, username, is_admin FROM users WHERE username = ?`, username)
|
row := db.QueryRow(`SELECT id, username, is_admin FROM users WHERE username = ?`, username) // ToDo: needs hash
|
||||||
var u models.User
|
var u models.User
|
||||||
err := row.Scan(&u.Id, &u.Username, &u.IsAdmin)
|
err := row.Scan(&u.Id, &u.Username, &u.IsAdmin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -258,3 +267,31 @@ func InviteToSyndicate(db *sql.DB, inviterID, syndicateID int, username string)
|
|||||||
`, syndicateID, inviteeID)
|
`, syndicateID, inviteeID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetInviteTokensForSyndicate(db *sql.DB, syndicateID int) []models.SyndicateInviteToken {
|
||||||
|
rows, err := db.Query(`
|
||||||
|
SELECT token, invited_by_user_id, accepted_by_user_id, created_at, expires_at, accepted_at
|
||||||
|
FROM syndicate_invite_tokens
|
||||||
|
WHERE syndicate_id = ?
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`, syndicateID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var tokens []models.SyndicateInviteToken
|
||||||
|
for rows.Next() {
|
||||||
|
var t models.SyndicateInviteToken
|
||||||
|
_ = rows.Scan(
|
||||||
|
&t.Token,
|
||||||
|
&t.InvitedByUserID,
|
||||||
|
&t.AcceptedByUserID,
|
||||||
|
&t.CreatedAt,
|
||||||
|
&t.ExpiresAt,
|
||||||
|
&t.AcceptedAt,
|
||||||
|
)
|
||||||
|
tokens = append(tokens, t)
|
||||||
|
}
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|||||||
57
templates/syndicate/manager/invite_links.html
Normal file
57
templates/syndicate/manager/invite_links.html
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{{ define "content" }}
|
||||||
|
<div class="container py-5">
|
||||||
|
<h2>Manage Invite Links</h2>
|
||||||
|
|
||||||
|
{{ if .Tokens }}
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Invite Link</th>
|
||||||
|
<th>Invited By</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Expires</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .Tokens }}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>/syndicate/join?token={{ .Token }}</code>
|
||||||
|
</td>
|
||||||
|
<td>User #{{ .InvitedByUserID }}</td>
|
||||||
|
<td>
|
||||||
|
{{ if .AcceptedByUserID.Valid }}
|
||||||
|
<span class="text-success" title="Joined on {{ .AcceptedAt.Time.Format \"02 Jan 2006 15:04\" }}">
|
||||||
|
Accepted by User #{{ .AcceptedByUserID.Int64 }}
|
||||||
|
</span>
|
||||||
|
{{ else if .ExpiresAt.Before (now) }}
|
||||||
|
<span class="text-danger" title="Expired on {{ .ExpiresAt.Format \"02 Jan 2006 15:04\" }}">Expired</span>
|
||||||
|
{{ else }}
|
||||||
|
<span class="text-warning" title="Expires in {{ humanizeTime .ExpiresAt }}">Pending</span>
|
||||||
|
{{ end }}
|
||||||
|
</td>
|
||||||
|
<td>{{ .ExpiresAt.Format "02 Jan 2006 15:04" }}</td>
|
||||||
|
<td>
|
||||||
|
{{ if not .AcceptedByUserID.Valid }}
|
||||||
|
<form method="POST" action="/account/syndicates/invite/revoke?token={{ .Token }}&id={{ $.SyndicateID }}" class="d-inline">
|
||||||
|
{{ $.CSRFField }}
|
||||||
|
<button class="btn btn-sm btn-outline-danger" title="Invalidate this token">Revoke</button>
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
||||||
|
<form method="POST" action="/account/syndicates/invite/token?id={{ $.SyndicateID }}" class="d-inline">
|
||||||
|
{{ $.CSRFField }}
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" title="Create a new invite token">Regenerate</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{ else }}
|
||||||
|
<div class="alert alert-info">No invite links found for this syndicate.</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<a href="/syndicate/view?id={{ .SyndicateID }}" class="btn btn-secondary mt-3">← Back</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
@@ -32,3 +32,7 @@
|
|||||||
<div class="alert alert-info mt-2">{{ .Flash }}</div>
|
<div class="alert alert-info mt-2">{{ .Flash }}</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
<a href="/syndicate" class="btn btn-secondary mt-3">← Back to Syndicates</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|||||||
@@ -111,9 +111,9 @@
|
|||||||
|
|
||||||
<!-- User Greeting -->
|
<!-- User Greeting -->
|
||||||
<span class="navbar-text">Hello, {{ .User.Username }}</span>
|
<span class="navbar-text">Hello, {{ .User.Username }}</span>
|
||||||
<a class="btn btn-outline-danger btn-xs" href="/logout">Logout</a>
|
<a class="btn btn-outline-danger btn-xs" href="/account/logout">Logout</a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a class="btn btn-outline-primary btn-sm" href="/login">Login</a>
|
<a class="btn btn-outline-primary btn-sm" href="/account/login">Login</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
Reference in New Issue
Block a user