added authentication among other things. considered working at this point.
This commit is contained in:
11
go.mod
11
go.mod
@@ -2,16 +2,23 @@ module synlotto-website
|
|||||||
|
|
||||||
go 1.24.1
|
go 1.24.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/csrf v1.7.2
|
||||||
|
github.com/gorilla/sessions v1.4.0
|
||||||
|
golang.org/x/crypto v0.36.0
|
||||||
|
modernc.org/sqlite v1.36.1
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
modernc.org/libc v1.61.13 // indirect
|
modernc.org/libc v1.61.13 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.8.2 // indirect
|
modernc.org/memory v1.8.2 // indirect
|
||||||
modernc.org/sqlite v1.36.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
38
go.sum
38
go.sum
@@ -1,23 +1,57 @@
|
|||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||||
|
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||||
|
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||||
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||||
|
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
||||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
|
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||||
|
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||||
|
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||||
|
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
|
||||||
|
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
|
||||||
|
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
|
||||||
|
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
|
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||||
|
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
|
||||||
|
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
|
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
|
||||||
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
|
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
|
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
|
||||||
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
|
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ=
|
modernc.org/sqlite v1.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ=
|
||||||
modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
|
modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
|||||||
69
handlers/account.go
Normal file
69
handlers/account.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"synlotto-website/models"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Login(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
tmpl := template.Must(template.ParseFiles(
|
||||||
|
"templates/layout.html",
|
||||||
|
"templates/account/login.html",
|
||||||
|
))
|
||||||
|
|
||||||
|
tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{
|
||||||
|
"csrfField": csrf.TemplateField(r),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := r.FormValue("username")
|
||||||
|
password := r.FormValue("password")
|
||||||
|
|
||||||
|
user := models.GetUserByUsername(username)
|
||||||
|
if user == nil || !CheckPasswordHash(user.PasswordHash, password) {
|
||||||
|
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, _ := GetSession(w, r)
|
||||||
|
session.Values["user_id"] = user.Id
|
||||||
|
session.Save(r, w)
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Signup(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
tmpl := template.Must(template.ParseFiles(
|
||||||
|
"templates/layout.html",
|
||||||
|
"templates/account/signup.html",
|
||||||
|
))
|
||||||
|
|
||||||
|
tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{
|
||||||
|
"csrfField": csrf.TemplateField(r),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := r.FormValue("username")
|
||||||
|
password := r.FormValue("password")
|
||||||
|
|
||||||
|
hashed, err := HashPassword(password)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.CreateUser(username, hashed)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Could not create user", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
|
}
|
||||||
13
handlers/auth.go
Normal file
13
handlers/auth.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import "golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
func HashPassword(password string) (string, error) {
|
||||||
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
return string(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckPasswordHash(hash, password string) bool {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
@@ -1,17 +1,8 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
|
||||||
"synlotto-website/models"
|
"synlotto-website/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Tmpl = template.Must(template.ParseFiles(
|
|
||||||
"templates/layout.html",
|
|
||||||
"templates/index.html",
|
|
||||||
"templates/new_draw.html",
|
|
||||||
"templates/new_ticket.html",
|
|
||||||
"templates/tickets.html",
|
|
||||||
))
|
|
||||||
|
|
||||||
var Draws []models.ThunderballResult
|
var Draws []models.ThunderballResult
|
||||||
var MyTickets []models.MyTicket
|
var MyTickets []models.MyTicket
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"synlotto-website/helpers"
|
"synlotto-website/helpers"
|
||||||
"synlotto-website/models"
|
"synlotto-website/models"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Home(w http.ResponseWriter, r *http.Request) {
|
func Home(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Println("✅ Home hit")
|
log.Println("✅ Home hit")
|
||||||
|
|
||||||
err := Tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{
|
tmpl := template.Must(template.ParseFiles(
|
||||||
"Page": "index",
|
"templates/layout.html",
|
||||||
|
"templates/index.html",
|
||||||
|
))
|
||||||
|
|
||||||
|
err := tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{
|
||||||
"Data": Draws,
|
"Data": Draws,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -23,9 +30,15 @@ func Home(w http.ResponseWriter, r *http.Request) {
|
|||||||
func NewDraw(w http.ResponseWriter, r *http.Request) {
|
func NewDraw(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Println("➡️ New draw form opened")
|
log.Println("➡️ New draw form opened")
|
||||||
|
|
||||||
err := Tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{
|
tmpl := template.Must(template.ParseFiles(
|
||||||
"Page": "new_draw",
|
"templates/layout.html",
|
||||||
"Data": nil,
|
"templates/new_draw.html",
|
||||||
|
))
|
||||||
|
|
||||||
|
err := tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{
|
||||||
|
"csrfField": csrf.TemplateField(r),
|
||||||
|
"Page": "new_draw",
|
||||||
|
"Data": nil,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("❌ Template error:", err)
|
log.Println("❌ Template error:", err)
|
||||||
|
|||||||
44
handlers/session.go
Normal file
44
handlers/session.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var store = sessions.NewCookieStore([]byte("super-secret-key")) // ToDo: Make global
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
store.Options = &sessions.Options{
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: 86400 * 1,
|
||||||
|
HttpOnly: true,
|
||||||
|
Secure: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSession(w http.ResponseWriter, r *http.Request) (*sessions.Session, error) {
|
||||||
|
return store.Get(r, "session-name")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCurrentUserID(r *http.Request) (int, bool) {
|
||||||
|
session, err := GetSession(nil, r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
id, ok := session.Values["user_id"].(int)
|
||||||
|
return id, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequireAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, ok := GetCurrentUserID(r)
|
||||||
|
if !ok {
|
||||||
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,21 +2,29 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"synlotto-website/helpers"
|
"synlotto-website/helpers"
|
||||||
"synlotto-website/models"
|
"synlotto-website/models"
|
||||||
"synlotto-website/storage"
|
"synlotto-website/storage"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTicket(db *sql.DB) http.HandlerFunc {
|
func NewTicket(db *sql.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Println("➡️ New ticket form opened")
|
log.Println("➡️ New ticket form opened")
|
||||||
|
|
||||||
err := Tmpl.ExecuteTemplate(w, "new_ticket", map[string]interface{}{
|
tmpl := template.Must(template.ParseFiles(
|
||||||
"Page": "new_ticket",
|
"templates/layout.html",
|
||||||
"Data": nil,
|
"templates/new_ticket.html",
|
||||||
|
))
|
||||||
|
|
||||||
|
err := tmpl.ExecuteTemplate(w, "layout", map[string]interface{}{
|
||||||
|
"csrfField": csrf.TemplateField(r),
|
||||||
|
"Page": "new_ticket",
|
||||||
|
"Data": nil,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("❌ Template error:", err)
|
log.Println("❌ Template error:", err)
|
||||||
@@ -27,6 +35,10 @@ func NewTicket(db *sql.DB) http.HandlerFunc {
|
|||||||
|
|
||||||
func SubmitTicket(db *sql.DB) http.HandlerFunc {
|
func SubmitTicket(db *sql.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if _, ok := GetCurrentUserID(r); !ok {
|
||||||
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
ticket := models.MyTicket{
|
ticket := models.MyTicket{
|
||||||
GameType: r.FormValue("game_type"),
|
GameType: r.FormValue("game_type"),
|
||||||
DrawDate: r.FormValue("draw_date"),
|
DrawDate: r.FormValue("draw_date"),
|
||||||
@@ -53,6 +65,11 @@ func ListTickets(db *sql.DB) http.HandlerFunc {
|
|||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Println("📋 Tickets page hit")
|
log.Println("📋 Tickets page hit")
|
||||||
|
|
||||||
|
tmpl := template.Must(template.ParseFiles(
|
||||||
|
"templates/layout.html",
|
||||||
|
"templates/tickets.html",
|
||||||
|
))
|
||||||
|
|
||||||
rows, err := db.Query(`
|
rows, err := db.Query(`
|
||||||
SELECT id, game_type, draw_date, ball1, ball2, ball3, ball4, ball5, bonus1, bonus2, duplicate
|
SELECT id, game_type, draw_date, ball1, ball2, ball3, ball4, ball5, bonus1, bonus2, duplicate
|
||||||
FROM my_tickets
|
FROM my_tickets
|
||||||
@@ -80,7 +97,7 @@ func ListTickets(db *sql.DB) http.HandlerFunc {
|
|||||||
tickets = append(tickets, t)
|
tickets = append(tickets, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Tmpl.ExecuteTemplate(w, "tickets", map[string]any{
|
err = tmpl.ExecuteTemplate(w, "layout", map[string]any{
|
||||||
"Page": "tickets",
|
"Page": "tickets",
|
||||||
"Data": tickets,
|
"Data": tickets,
|
||||||
})
|
})
|
||||||
|
|||||||
39
main.go
39
main.go
@@ -4,31 +4,32 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"synlotto-website/handlers"
|
"synlotto-website/handlers"
|
||||||
|
"synlotto-website/models"
|
||||||
"synlotto-website/storage"
|
"synlotto-website/storage"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
db := storage.InitDB("synlotto.db")
|
db := storage.InitDB("synlotto.db")
|
||||||
|
models.SetDB(db)
|
||||||
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
csrfMiddleware := csrf.Protect(
|
||||||
switch r.URL.Path {
|
[]byte("32-byte-long-auth-key-here"), // TodO: Make Global
|
||||||
case "/":
|
csrf.Secure(false),
|
||||||
handlers.Home(w, r)
|
)
|
||||||
case "/new":
|
|
||||||
handlers.NewDraw(w, r)
|
mux := http.NewServeMux()
|
||||||
case "/submit":
|
|
||||||
handlers.Submit(w, r)
|
mux.HandleFunc("/", handlers.Home)
|
||||||
case "/ticket":
|
mux.HandleFunc("/new", handlers.NewDraw)
|
||||||
handlers.NewTicket(db)
|
mux.HandleFunc("/submit", handlers.Submit)
|
||||||
case "/tickets":
|
mux.HandleFunc("/ticket", handlers.NewTicket(db))
|
||||||
handlers.ListTickets(db)
|
mux.HandleFunc("/tickets", handlers.ListTickets(db))
|
||||||
case "/submit-ticket":
|
mux.HandleFunc("/submit-ticket", handlers.RequireAuth(handlers.SubmitTicket(db)))
|
||||||
handlers.SubmitTicket(db)
|
mux.HandleFunc("/login", handlers.Login)
|
||||||
default:
|
mux.HandleFunc("/signup", handlers.Signup)
|
||||||
http.NotFound(w, r)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
log.Println("🌐 Running on http://localhost:8080")
|
log.Println("🌐 Running on http://localhost:8080")
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
http.ListenAndServe(":8080", csrfMiddleware(mux))
|
||||||
}
|
}
|
||||||
|
|||||||
37
models/user.go
Normal file
37
models/user.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Id int
|
||||||
|
Username string
|
||||||
|
PasswordHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
|
||||||
|
func SetDB(database *sql.DB) {
|
||||||
|
db = database
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateUser(username, passwordHash string) error {
|
||||||
|
_, err := db.Exec("INSERT INTO users (username, password_hash) VALUES (?, ?)", username, passwordHash)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByUsername(username string) *User {
|
||||||
|
row := db.QueryRow("SELECT id, username, password_hash FROM users WHERE username = ?", username)
|
||||||
|
|
||||||
|
var user User
|
||||||
|
err := row.Scan(&user.Id, &user.Username, &user.PasswordHash)
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
log.Println("DB error:", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &user
|
||||||
|
}
|
||||||
@@ -27,7 +27,11 @@ func InitDB(filepath string) *sql.DB {
|
|||||||
thunderball INTEGER
|
thunderball INTEGER
|
||||||
);`
|
);`
|
||||||
|
|
||||||
createMyTickets := `
|
if _, err := db.Exec(createThunderballResultsTable); err != nil {
|
||||||
|
log.Fatal("❌ Failed to create Thunderball table:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createMyTicketsTable := `
|
||||||
CREATE TABLE IF NOT EXISTS my_tickets (
|
CREATE TABLE IF NOT EXISTS my_tickets (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
game_type TEXT NOT NULL,
|
game_type TEXT NOT NULL,
|
||||||
@@ -43,12 +47,20 @@ func InitDB(filepath string) *sql.DB {
|
|||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);`
|
);`
|
||||||
|
|
||||||
if _, err := db.Exec(createThunderballResultsTable); err != nil {
|
if _, err := db.Exec(createMyTicketsTable); err != nil {
|
||||||
log.Fatal("❌ Failed to create Thunderball table:", err)
|
|
||||||
}
|
|
||||||
if _, err := db.Exec(createMyTickets); err != nil {
|
|
||||||
log.Fatal("❌ Failed to create MyTickets table:", err)
|
log.Fatal("❌ Failed to create MyTickets table:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createUsersTable := `
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
password_hash TEXT NOT NULL
|
||||||
|
);`
|
||||||
|
|
||||||
|
if _, err := db.Exec(createUsersTable); err != nil {
|
||||||
|
log.Fatal("❌ Failed to create Users table:", err)
|
||||||
|
}
|
||||||
|
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
synlotto.db
BIN
synlotto.db
Binary file not shown.
9
templates/account/login.html
Normal file
9
templates/account/login.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{{ define "content" }}
|
||||||
|
<h2>Login</h2>
|
||||||
|
<form method="POST" action="/login">
|
||||||
|
{{ .csrfField }}
|
||||||
|
<label>Username: <input type="text" name="username" required></label><br>
|
||||||
|
<label>Password: <input type="password" name="password" required></label><br>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
||||||
9
templates/account/signup.html
Normal file
9
templates/account/signup.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{{ define "content" }}
|
||||||
|
<h2>Sign Up</h2>
|
||||||
|
<form method="POST" action="/signup">
|
||||||
|
{{ .csrfField }}
|
||||||
|
<label>Username: <input type="text" name="username" required></label><br>
|
||||||
|
<label>Password: <input type="password" name="password" required></label><br>
|
||||||
|
<button type="submit">Sign Up</button>
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{{ define "index" }}
|
{{ define "content" }}
|
||||||
<a href="/new">+ Add New Draw</a>
|
<a href="/new">+ Add New Draw</a>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -15,11 +15,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>Lotto Tracker</h1>
|
<h1>Lotto Tracker</h1>
|
||||||
|
|
||||||
{{ if eq .Page "index" }}
|
{{ template "content" . }}
|
||||||
{{ template "index" .Data }}
|
|
||||||
{{ else if eq .Page "new_draw" }}
|
|
||||||
{{ template "new_draw" .Data }}
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{{ define "new_draw" }}
|
{{ define "content" }}
|
||||||
<a href="/">← Back</a>
|
<a href="/">← Back</a>
|
||||||
<h2>Add New Thunderball Draw</h2>
|
<h2>Add New Thunderball Draw</h2>
|
||||||
<form method="POST" action="/submit">
|
<form method="POST" action="/submit">
|
||||||
|
{{ .csrfField }}
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<label>Date: <input type="date" name="date" required></label>
|
<label>Date: <input type="date" name="date" required></label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{{ define "new_ticket" }}
|
{{ define "content" }}
|
||||||
<a href="/">← Back</a>
|
<a href="/">← Back</a>
|
||||||
<h2>Log My Ticket</h2>
|
<h2>Log My Ticket</h2>
|
||||||
<form method="POST" action="/submit-ticket">
|
<form method="POST" action="/submit-ticket">
|
||||||
|
{{ .csrfField }}
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<label>Draw Date:
|
<label>Draw Date:
|
||||||
<select name="draw_date" required>
|
<select name="draw_date" required>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{{ define "tickets" }}
|
{{ define "content" }}
|
||||||
{{ define "content" }}
|
|
||||||
<a href="/">← Back to Home</a>
|
<a href="/">← Back to Home</a>
|
||||||
<h2>My Tickets</h2>
|
<h2>My Tickets</h2>
|
||||||
|
|
||||||
@@ -32,5 +31,4 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{{ template "layout" . }}
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user