Compare commits
	
		
			15 Commits
		
	
	
		
			toml_confi
			...
			6d6aff50b3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					6d6aff50b3 | ||
| 
						 | 
					a6be73765a | ||
| 
						 | 
					ddc9e51831 | ||
| 
						 | 
					dc450e26dd | ||
| 
						 | 
					de4a217c5f | ||
| 
						 | 
					c4e83d06b9 | ||
| 
						 | 
					51da24be9b | ||
| 
						 | 
					e497f4d2f0 | ||
| 
						 | 
					b30af86e58 | ||
| 
						 | 
					3ffd548623 | ||
| 
						 | 
					cb4f10e0b4 | ||
| 
						 | 
					878ce01b29 | ||
| 
						 | 
					c82cdb4f13 | ||
| 
						 | 
					ce81c36e9f | ||
| 
						 | 
					ab1b82c680 | 
@@ -17,8 +17,8 @@ type Scheduled struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Task struct {
 | 
					type Task struct {
 | 
				
			||||||
	Interval time.Duration
 | 
					 | 
				
			||||||
	Funcs    []func(app *App)
 | 
						Funcs    []func(app *App)
 | 
				
			||||||
 | 
						Interval time.Duration
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RunScheduledTasks(app *App, poolSize int, stop <-chan struct{}) {
 | 
					func RunScheduledTasks(app *App, poolSize int, stop <-chan struct{}) {
 | 
				
			||||||
@@ -27,13 +27,13 @@ func RunScheduledTasks(app *App, poolSize int, stop <-chan struct{}) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tasks := []Task{
 | 
						tasks := []Task{
 | 
				
			||||||
		{Interval: time.Second, Funcs: app.ScheduledTasks.EverySecond},
 | 
							{Funcs: app.ScheduledTasks.EverySecond, Interval: time.Second},
 | 
				
			||||||
		{Interval: time.Minute, Funcs: app.ScheduledTasks.EveryMinute},
 | 
							{Funcs: app.ScheduledTasks.EveryMinute, Interval: time.Minute},
 | 
				
			||||||
		{Interval: time.Hour, Funcs: app.ScheduledTasks.EveryHour},
 | 
							{Funcs: app.ScheduledTasks.EveryHour, Interval: time.Hour},
 | 
				
			||||||
		{Interval: 24 * time.Hour, Funcs: app.ScheduledTasks.EveryDay},
 | 
							{Funcs: app.ScheduledTasks.EveryDay, Interval: 24 * time.Hour},
 | 
				
			||||||
		{Interval: 7 * 24 * time.Hour, Funcs: app.ScheduledTasks.EveryWeek},
 | 
							{Funcs: app.ScheduledTasks.EveryWeek, Interval: 7 * 24 * time.Hour},
 | 
				
			||||||
		{Interval: 30 * 24 * time.Hour, Funcs: app.ScheduledTasks.EveryMonth},
 | 
							{Funcs: app.ScheduledTasks.EveryMonth, Interval: 30 * 24 * time.Hour},
 | 
				
			||||||
		{Interval: 365 * 24 * time.Hour, Funcs: app.ScheduledTasks.EveryYear},
 | 
							{Funcs: app.ScheduledTasks.EveryYear, Interval: 365 * 24 * time.Hour},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var wg sync.WaitGroup
 | 
						var wg sync.WaitGroup
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,8 @@ type Configuration struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Template struct {
 | 
						Template struct {
 | 
				
			||||||
		BaseName string `json:"BaseTemplateName"`
 | 
							BaseName    string `json:"BaseTemplateName"`
 | 
				
			||||||
 | 
							ContentPath string `json:"ContentPath"`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,21 +13,37 @@ type Get struct {
 | 
				
			|||||||
	App *app.App
 | 
						App *app.App
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (g *Get) ShowHome(w http.ResponseWriter, _ *http.Request) {
 | 
					func (g *Get) ShowHome(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	type dataStruct struct {
 | 
						type dataStruct struct {
 | 
				
			||||||
		Test string
 | 
							CsrfToken       string
 | 
				
			||||||
 | 
							IsAuthenticated bool
 | 
				
			||||||
 | 
							Test            string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CsrfToken, err := security.GenerateCsrfToken(w, r)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						isAuthenticated := true
 | 
				
			||||||
 | 
						user, err := models.CurrentUser(g.App, r)
 | 
				
			||||||
 | 
						if err != nil || user.Id == 0 {
 | 
				
			||||||
 | 
							isAuthenticated = false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data := dataStruct{
 | 
						data := dataStruct{
 | 
				
			||||||
		Test: "Hello World!",
 | 
							CsrfToken:       CsrfToken,
 | 
				
			||||||
 | 
							Test:            "Hello World!",
 | 
				
			||||||
 | 
							IsAuthenticated: isAuthenticated,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	templating.RenderTemplate(g.App, w, "templates/pages/home.html", data)
 | 
						templating.RenderTemplate(w, "templates/pages/home.html", data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (g *Get) ShowRegister(w http.ResponseWriter, r *http.Request) {
 | 
					func (g *Get) ShowRegister(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	type dataStruct struct {
 | 
						type dataStruct struct {
 | 
				
			||||||
		CsrfToken string
 | 
							CsrfToken       string
 | 
				
			||||||
 | 
							IsAuthenticated bool
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CsrfToken, err := security.GenerateCsrfToken(w, r)
 | 
						CsrfToken, err := security.GenerateCsrfToken(w, r)
 | 
				
			||||||
@@ -35,16 +51,24 @@ func (g *Get) ShowRegister(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data := dataStruct{
 | 
						isAuthenticated := true
 | 
				
			||||||
		CsrfToken: CsrfToken,
 | 
						user, err := models.CurrentUser(g.App, r)
 | 
				
			||||||
 | 
						if err != nil || user.Id == 0 {
 | 
				
			||||||
 | 
							isAuthenticated = false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	templating.RenderTemplate(g.App, w, "templates/pages/register.html", data)
 | 
						data := dataStruct{
 | 
				
			||||||
 | 
							CsrfToken:       CsrfToken,
 | 
				
			||||||
 | 
							IsAuthenticated: isAuthenticated,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						templating.RenderTemplate(w, "templates/pages/register.html", data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (g *Get) ShowLogin(w http.ResponseWriter, r *http.Request) {
 | 
					func (g *Get) ShowLogin(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	type dataStruct struct {
 | 
						type dataStruct struct {
 | 
				
			||||||
		CsrfToken string
 | 
							CsrfToken       string
 | 
				
			||||||
 | 
							IsAuthenticated bool
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CsrfToken, err := security.GenerateCsrfToken(w, r)
 | 
						CsrfToken, err := security.GenerateCsrfToken(w, r)
 | 
				
			||||||
@@ -56,10 +80,5 @@ func (g *Get) ShowLogin(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
		CsrfToken: CsrfToken,
 | 
							CsrfToken: CsrfToken,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	templating.RenderTemplate(g.App, w, "templates/pages/login.html", data)
 | 
						templating.RenderTemplate(w, "templates/pages/login.html", data)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (g *Get) Logout(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
	models.LogoutUser(g.App, w, r)
 | 
					 | 
				
			||||||
	http.Redirect(w, r, "/", http.StatusFound)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,3 +50,8 @@ func (p *Post) Register(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	http.Redirect(w, r, "/login", http.StatusFound)
 | 
						http.Redirect(w, r, "/login", http.StatusFound)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *Post) Logout(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						models.LogoutUser(p.App, w, r)
 | 
				
			||||||
 | 
						http.Redirect(w, r, "/", http.StatusFound)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,8 @@ import (
 | 
				
			|||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Migrate given a dummy object of any type, it will create a table with the same name as the type and create columns with the same name as the fields of the object
 | 
					// Migrate given a dummy object of any type, it will create a table with the same name
 | 
				
			||||||
 | 
					// as the type and create columns with the same name as the fields of the object
 | 
				
			||||||
func Migrate(app *app.App, anyStruct interface{}) error {
 | 
					func Migrate(app *app.App, anyStruct interface{}) error {
 | 
				
			||||||
	valueOfStruct := reflect.ValueOf(anyStruct)
 | 
						valueOfStruct := reflect.ValueOf(anyStruct)
 | 
				
			||||||
	typeOfStruct := valueOfStruct.Type()
 | 
						typeOfStruct := valueOfStruct.Type()
 | 
				
			||||||
@@ -23,10 +24,15 @@ func Migrate(app *app.App, anyStruct interface{}) error {
 | 
				
			|||||||
	for i := 0; i < valueOfStruct.NumField(); i++ {
 | 
						for i := 0; i < valueOfStruct.NumField(); i++ {
 | 
				
			||||||
		fieldType := typeOfStruct.Field(i)
 | 
							fieldType := typeOfStruct.Field(i)
 | 
				
			||||||
		fieldName := fieldType.Name
 | 
							fieldName := fieldType.Name
 | 
				
			||||||
		if fieldName != "Id" && fieldName != "id" {
 | 
					
 | 
				
			||||||
			err := createColumn(app, tableName, fieldName, fieldType.Type.Name())
 | 
							// Create column if dummy for migration is NOT zero value
 | 
				
			||||||
			if err != nil {
 | 
							fieldValue := valueOfStruct.Field(i).Interface()
 | 
				
			||||||
				return err
 | 
							if !reflect.ValueOf(fieldValue).IsZero() {
 | 
				
			||||||
 | 
								if fieldName != "Id" && fieldName != "id" {
 | 
				
			||||||
 | 
									err := createColumn(app, tableName, fieldName, fieldType.Type.Name())
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@
 | 
				
			|||||||
    "HttpPort": "8090"
 | 
					    "HttpPort": "8090"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "Template": {
 | 
					  "Template": {
 | 
				
			||||||
    "BaseTemplateName": "templates/base.html"
 | 
					    "BaseTemplateName": "templates/base.html",
 | 
				
			||||||
 | 
					    "ContentPath": "templates"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							@@ -1,8 +1,8 @@
 | 
				
			|||||||
module GoWeb
 | 
					module GoWeb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go 1.21
 | 
					go 1.22
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/lib/pq v1.10.9
 | 
						github.com/lib/pq v1.10.9
 | 
				
			||||||
	golang.org/x/crypto v0.13.0
 | 
						golang.org/x/crypto v0.19.0
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							@@ -1,4 +1,4 @@
 | 
				
			|||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 | 
					github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 | 
				
			||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
					github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
				
			||||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
 | 
					golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
 | 
				
			||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 | 
					golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								main.go
									
									
									
									
									
								
							@@ -6,6 +6,7 @@ import (
 | 
				
			|||||||
	"GoWeb/database"
 | 
						"GoWeb/database"
 | 
				
			||||||
	"GoWeb/models"
 | 
						"GoWeb/models"
 | 
				
			||||||
	"GoWeb/routes"
 | 
						"GoWeb/routes"
 | 
				
			||||||
 | 
						"GoWeb/templating"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"embed"
 | 
						"embed"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
@@ -67,6 +68,13 @@ func main() {
 | 
				
			|||||||
	routes.Get(&appLoaded)
 | 
						routes.Get(&appLoaded)
 | 
				
			||||||
	routes.Post(&appLoaded)
 | 
						routes.Post(&appLoaded)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Prepare templates
 | 
				
			||||||
 | 
						err = templating.BuildPages(&appLoaded)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							slog.Error("error building templates: " + err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Start server
 | 
						// Start server
 | 
				
			||||||
	server := &http.Server{Addr: appLoaded.Config.Listen.Ip + ":" + appLoaded.Config.Listen.Port}
 | 
						server := &http.Server{Addr: appLoaded.Config.Listen.Ip + ":" + appLoaded.Config.Listen.Port}
 | 
				
			||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@ func RunAllMigrations(app *app.App) error {
 | 
				
			|||||||
		Id:         1,
 | 
							Id:         1,
 | 
				
			||||||
		UserId:     1,
 | 
							UserId:     1,
 | 
				
			||||||
		AuthToken:  "migrate",
 | 
							AuthToken:  "migrate",
 | 
				
			||||||
		RememberMe: false,
 | 
							RememberMe: true, // Booleans must be true to migrate properly
 | 
				
			||||||
		CreatedAt:  time.Now(),
 | 
							CreatedAt:  time.Now(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err = database.Migrate(app, session)
 | 
						err = database.Migrate(app, session)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ type Session struct {
 | 
				
			|||||||
	CreatedAt  time.Time
 | 
						CreatedAt  time.Time
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sessionColumnsNoId = "\"UserId\", \"AuthToken\",\"RememberMe\", \"CreatedAt\""
 | 
					const sessionColumnsNoId = "\"UserId\", \"AuthToken\", \"RememberMe\", \"CreatedAt\""
 | 
				
			||||||
const sessionColumns = "\"Id\", " + sessionColumnsNoId
 | 
					const sessionColumns = "\"Id\", " + sessionColumnsNoId
 | 
				
			||||||
const sessionTable = "public.\"Session\""
 | 
					const sessionTable = "public.\"Session\""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -62,7 +62,7 @@ func CreateSession(app *app.App, w http.ResponseWriter, userId int64, remember b
 | 
				
			|||||||
	return session, nil
 | 
						return session, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetSessionByAuthToken(app *app.App, authToken string) (Session, error) {
 | 
					func SessionByAuthToken(app *app.App, authToken string) (Session, error) {
 | 
				
			||||||
	session := Session{}
 | 
						session := Session{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := app.Db.QueryRow(selectSessionByAuthToken, authToken).Scan(&session.Id, &session.UserId, &session.AuthToken, &session.RememberMe, &session.CreatedAt)
 | 
						err := app.Db.QueryRow(selectSessionByAuthToken, authToken).Scan(&session.Id, &session.UserId, &session.AuthToken, &session.RememberMe, &session.CreatedAt)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,8 @@ package models
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"GoWeb/app"
 | 
						"GoWeb/app"
 | 
				
			||||||
 | 
						"crypto/sha256"
 | 
				
			||||||
 | 
						"encoding/hex"
 | 
				
			||||||
	"log/slog"
 | 
						"log/slog"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -27,23 +29,23 @@ const (
 | 
				
			|||||||
	insertUser           = "INSERT INTO " + userTable + " (" + userColumnsNoId + ") VALUES ($1, $2, $3, $4) RETURNING \"Id\""
 | 
						insertUser           = "INSERT INTO " + userTable + " (" + userColumnsNoId + ") VALUES ($1, $2, $3, $4) RETURNING \"Id\""
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetCurrentUser finds the currently logged-in user by session cookie
 | 
					// CurrentUser finds the currently logged-in user by session cookie
 | 
				
			||||||
func GetCurrentUser(app *app.App, r *http.Request) (User, error) {
 | 
					func CurrentUser(app *app.App, r *http.Request) (User, error) {
 | 
				
			||||||
	cookie, err := r.Cookie("session")
 | 
						cookie, err := r.Cookie("session")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return User{}, err
 | 
							return User{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	session, err := GetSessionByAuthToken(app, cookie.Value)
 | 
						session, err := SessionByAuthToken(app, cookie.Value)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return User{}, err
 | 
							return User{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return GetUserById(app, session.UserId)
 | 
						return UserById(app, session.UserId)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUserById finds a User table row in the database by id and returns a struct representing this row
 | 
					// UserById finds a User table row in the database by id and returns a struct representing this row
 | 
				
			||||||
func GetUserById(app *app.App, id int64) (User, error) {
 | 
					func UserById(app *app.App, id int64) (User, error) {
 | 
				
			||||||
	user := User{}
 | 
						user := User{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := app.Db.QueryRow(selectUserById, id).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt)
 | 
						err := app.Db.QueryRow(selectUserById, id).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt)
 | 
				
			||||||
@@ -54,8 +56,8 @@ func GetUserById(app *app.App, id int64) (User, error) {
 | 
				
			|||||||
	return user, nil
 | 
						return user, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetUserByUsername finds a User table row in the database by username and returns a struct representing this row
 | 
					// UserByUsername finds a User table row in the database by username and returns a struct representing this row
 | 
				
			||||||
func GetUserByUsername(app *app.App, username string) (User, error) {
 | 
					func UserByUsername(app *app.App, username string) (User, error) {
 | 
				
			||||||
	user := User{}
 | 
						user := User{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := app.Db.QueryRow(selectUserByUsername, username).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt)
 | 
						err := app.Db.QueryRow(selectUserByUsername, username).Scan(&user.Id, &user.Username, &user.Password, &user.CreatedAt, &user.UpdatedAt)
 | 
				
			||||||
@@ -68,7 +70,12 @@ func GetUserByUsername(app *app.App, username string) (User, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// CreateUser creates a User table row in the database
 | 
					// CreateUser creates a User table row in the database
 | 
				
			||||||
func CreateUser(app *app.App, username string, password string, createdAt time.Time, updatedAt time.Time) (User, error) {
 | 
					func CreateUser(app *app.App, username string, password string, createdAt time.Time, updatedAt time.Time) (User, error) {
 | 
				
			||||||
	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
 | 
						// Get sha256 hash of password then get bcrypt hash to store
 | 
				
			||||||
 | 
						hash256 := sha256.New()
 | 
				
			||||||
 | 
						hash256.Write([]byte(password))
 | 
				
			||||||
 | 
						hashSum := hash256.Sum(nil)
 | 
				
			||||||
 | 
						hashString := hex.EncodeToString(hashSum)
 | 
				
			||||||
 | 
						hash, err := bcrypt.GenerateFromPassword([]byte(hashString), bcrypt.DefaultCost)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		slog.Error("error hashing password: " + err.Error())
 | 
							slog.Error("error hashing password: " + err.Error())
 | 
				
			||||||
		return User{}, err
 | 
							return User{}, err
 | 
				
			||||||
@@ -82,7 +89,7 @@ func CreateUser(app *app.App, username string, password string, createdAt time.T
 | 
				
			|||||||
		return User{}, err
 | 
							return User{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return GetUserById(app, lastInsertId)
 | 
						return UserById(app, lastInsertId)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AuthenticateUser validates the password for the specified user
 | 
					// AuthenticateUser validates the password for the specified user
 | 
				
			||||||
@@ -95,7 +102,12 @@ func AuthenticateUser(app *app.App, w http.ResponseWriter, username string, pass
 | 
				
			|||||||
		return Session{}, err
 | 
							return Session{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
 | 
						// Get sha256 hash of password then check bcrypt
 | 
				
			||||||
 | 
						hash256 := sha256.New()
 | 
				
			||||||
 | 
						hash256.Write([]byte(password))
 | 
				
			||||||
 | 
						hashSum := hash256.Sum(nil)
 | 
				
			||||||
 | 
						hashString := hex.EncodeToString(hashSum)
 | 
				
			||||||
 | 
						err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(hashString))
 | 
				
			||||||
	if err != nil { // Failed to validate password, doesn't match
 | 
						if err != nil { // Failed to validate password, doesn't match
 | 
				
			||||||
		slog.Info("incorrect password:" + username)
 | 
							slog.Info("incorrect password:" + username)
 | 
				
			||||||
		return Session{}, err
 | 
							return Session{}, err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,12 +22,11 @@ func Get(app *app.App) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	staticHandler := http.FileServer(http.FS(staticFS))
 | 
						staticHandler := http.FileServer(http.FS(staticFS))
 | 
				
			||||||
	http.Handle("/static/", http.StripPrefix("/static/", staticHandler))
 | 
						http.Handle("GET /static/", http.StripPrefix("/static/", staticHandler))
 | 
				
			||||||
	slog.Info("serving static files from embedded file system /static")
 | 
						slog.Info("serving static files from embedded file system /static")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Pages
 | 
						// Pages
 | 
				
			||||||
	http.HandleFunc("/", getController.ShowHome)
 | 
						http.HandleFunc("GET /", getController.ShowHome)
 | 
				
			||||||
	http.HandleFunc("/login", getController.ShowLogin)
 | 
						http.HandleFunc("GET /login", getController.ShowLogin)
 | 
				
			||||||
	http.HandleFunc("/register", getController.ShowRegister)
 | 
						http.HandleFunc("GET /register", getController.ShowRegister)
 | 
				
			||||||
	http.HandleFunc("/logout", getController.Logout)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ func Post(app *app.App) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// User authentication
 | 
						// User authentication
 | 
				
			||||||
	http.HandleFunc("/register-handle", middleware.Csrf(postController.Register))
 | 
						http.HandleFunc("POST /register-handle", middleware.Csrf(postController.Register))
 | 
				
			||||||
	http.HandleFunc("/login-handle", middleware.Csrf(postController.Login))
 | 
						http.HandleFunc("POST /login-handle", middleware.Csrf(postController.Login))
 | 
				
			||||||
 | 
						http.HandleFunc("POST /logout", middleware.Csrf(postController.Logout))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,9 +3,24 @@
 | 
				
			|||||||
<head>
 | 
					<head>
 | 
				
			||||||
    <meta charset="UTF-8">
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
    <title>SiteName - {{ template "pageTitle" }}</title>
 | 
					    <title>SiteName - {{ template "pageTitle" }}</title>
 | 
				
			||||||
    <link rel="stylesheet" href="/static/css/style.css">
 | 
					    <link href="/static/css/style.css" rel="stylesheet">
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
 | 
					<div class="navbar">
 | 
				
			||||||
 | 
					    {{ if .IsAuthenticated }}
 | 
				
			||||||
 | 
					    <form action="/logout" method="post">
 | 
				
			||||||
 | 
					        <input name="csrf_token" type="hidden" value="{{ .CsrfToken }}">
 | 
				
			||||||
 | 
					        <input type="submit" value="Logout">
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					    {{ else }}
 | 
				
			||||||
 | 
					    <form action="/login" method="get">
 | 
				
			||||||
 | 
					        <input type="submit" value="Login">
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					    <form action="/register" method="get">
 | 
				
			||||||
 | 
					        <input type="submit" value="Register">
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					    {{ end }}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
{{ template "content" . }}
 | 
					{{ template "content" . }}
 | 
				
			||||||
<div class="footer-container">
 | 
					<div class="footer-container">
 | 
				
			||||||
    <footer>
 | 
					    <footer>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,11 +7,11 @@
 | 
				
			|||||||
        <input name="csrf_token" type="hidden" value="{{ .CsrfToken }}">
 | 
					        <input name="csrf_token" type="hidden" value="{{ .CsrfToken }}">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <label for="username">Username:</label><br>
 | 
					        <label for="username">Username:</label><br>
 | 
				
			||||||
        <input id="username" name="username" type="text" placeholder="John"><br><br>
 | 
					        <input id="username" name="username" placeholder="John" type="text"><br><br>
 | 
				
			||||||
        <label for="password">Password:</label><br>
 | 
					        <label for="password">Password:</label><br>
 | 
				
			||||||
        <input id="password" name="password" type="password"><br><br>
 | 
					        <input id="password" name="password" type="password"><br><br>
 | 
				
			||||||
        <label for="remember">Remember Me:</label>
 | 
					        <label for="remember">Remember Me:</label>
 | 
				
			||||||
        <input id="remember" type="checkbox" name="remember"><br><br>
 | 
					        <input id="remember" name="remember" type="checkbox"><br><br>
 | 
				
			||||||
        <input type="submit" value="Submit">
 | 
					        <input type="submit" value="Submit">
 | 
				
			||||||
    </form>
 | 
					    </form>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@
 | 
				
			|||||||
        <input name="csrf_token" type="hidden" value="{{ .CsrfToken }}">
 | 
					        <input name="csrf_token" type="hidden" value="{{ .CsrfToken }}">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <label for="username">Username:</label><br>
 | 
					        <label for="username">Username:</label><br>
 | 
				
			||||||
        <input id="username" name="username" type="text" placeholder="John"><br><br>
 | 
					        <input id="username" name="username" placeholder="John" type="text"><br><br>
 | 
				
			||||||
        <label for="password">Password:</label><br>
 | 
					        <label for="password">Password:</label><br>
 | 
				
			||||||
        <input id="password" name="password" type="password"><br><br>
 | 
					        <input id="password" name="password" type="password"><br><br>
 | 
				
			||||||
        <input type="submit" value="Submit">
 | 
					        <input type="submit" value="Submit">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,45 +2,82 @@ package templating
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"GoWeb/app"
 | 
						"GoWeb/app"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
 | 
						"io/fs"
 | 
				
			||||||
	"log/slog"
 | 
						"log/slog"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RenderTemplate renders and serves a template from the embedded filesystem optionally with given data
 | 
					var templates = make(map[string]*template.Template) // This is only used here, does not need to be in app.App
 | 
				
			||||||
func RenderTemplate(app *app.App, w http.ResponseWriter, contentPath string, data any) {
 | 
					 | 
				
			||||||
	templatePath := app.Config.Template.BaseName
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	templateContent, err := app.Res.ReadFile(templatePath)
 | 
					func BuildPages(app *app.App) error {
 | 
				
			||||||
 | 
						basePath := app.Config.Template.BaseName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						baseContent, err := app.Res.ReadFile(basePath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("error reading base file: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						base, err := template.New(basePath).Parse(string(baseContent)) // Sets filepath as name and parses content
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("error parsing base file: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						readFilesRecursively := func(fsys fs.FS, root string) ([]string, error) {
 | 
				
			||||||
 | 
							var files []string
 | 
				
			||||||
 | 
							err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("error walking the path %q: %w", path, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !d.IsDir() {
 | 
				
			||||||
 | 
									files = append(files, path)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return files, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get all file paths in the directory tree
 | 
				
			||||||
 | 
						filePaths, err := readFilesRecursively(app.Res, app.Config.Template.ContentPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("error reading files recursively: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, contentPath := range filePaths { // Create a new template base + content for each page
 | 
				
			||||||
 | 
							content, err := app.Res.ReadFile(contentPath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("error reading content file %s: %w", contentPath, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							t, err := base.Clone()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("error cloning base template: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_, err = t.Parse(string(content))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("error parsing content: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							templates[contentPath] = t
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func RenderTemplate(w http.ResponseWriter, contentPath string, data any) {
 | 
				
			||||||
 | 
						t, ok := templates[contentPath]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							err := fmt.Errorf("template not found for path: %s", contentPath)
 | 
				
			||||||
		slog.Error(err.Error())
 | 
							slog.Error(err.Error())
 | 
				
			||||||
		http.Error(w, err.Error(), 500)
 | 
							http.Error(w, "Template not found", 404)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	t, err := template.New(templatePath).Parse(string(templateContent))
 | 
						err := t.Execute(w, data) // Execute prebuilt template with dynamic data
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		slog.Error(err.Error())
 | 
					 | 
				
			||||||
		http.Error(w, err.Error(), 500)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	content, err := app.Res.ReadFile(contentPath)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		slog.Error(err.Error())
 | 
					 | 
				
			||||||
		http.Error(w, err.Error(), 500)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t, err = t.Parse(string(content))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		slog.Error(err.Error())
 | 
					 | 
				
			||||||
		http.Error(w, err.Error(), 500)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = t.Execute(w, data)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							err = fmt.Errorf("error executing template: %w", err)
 | 
				
			||||||
		slog.Error(err.Error())
 | 
							slog.Error(err.Error())
 | 
				
			||||||
		http.Error(w, err.Error(), 500)
 | 
							http.Error(w, err.Error(), 500)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user