Compare commits
	
		
			4 Commits
		
	
	
		
			6d6aff50b3
			...
			toml_confi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					308198ee8b | ||
| 
						 | 
					ac19e2515a | ||
| 
						 | 
					60006b6e4e | ||
| 
						 | 
					72e9ee3e43 | 
@@ -18,7 +18,7 @@ fine with getting your hands dirty, but I plan on having it ready to go for more
 | 
				
			|||||||
- Minimal user login/registration + sessions
 | 
					- Minimal user login/registration + sessions
 | 
				
			||||||
- Config file handling
 | 
					- Config file handling
 | 
				
			||||||
- Scheduled tasks
 | 
					- Scheduled tasks
 | 
				
			||||||
- Entire website compiles into a single binary (~10mb) (excluding env.json)
 | 
					- Entire website compiles into a single binary (~10mb) (excluding env.toml)
 | 
				
			||||||
- Minimal dependencies (just standard library, postgres driver, and experimental package for bcrypt)
 | 
					- Minimal dependencies (just standard library, postgres driver, and experimental package for bcrypt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<hr>
 | 
					<hr>
 | 
				
			||||||
@@ -41,7 +41,7 @@ fine with getting your hands dirty, but I plan on having it ready to go for more
 | 
				
			|||||||
1. Clone
 | 
					1. Clone
 | 
				
			||||||
2. Delete the git folder, so you can start tracking in your own repo
 | 
					2. Delete the git folder, so you can start tracking in your own repo
 | 
				
			||||||
3. Run `go get` to install dependencies
 | 
					3. Run `go get` to install dependencies
 | 
				
			||||||
4. Copy env_example.json to env.json and fill in the values
 | 
					4. Copy env_example.toml to env.toml and fill in the values
 | 
				
			||||||
5. Run `go run main.go` to start the server
 | 
					5. Run `go run main.go` to start the server
 | 
				
			||||||
6. Rename the occurences of "GoWeb" to your app name
 | 
					6. Rename the occurences of "GoWeb" to your app name
 | 
				
			||||||
7. Start building your app!
 | 
					7. Start building your app!
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,8 +17,8 @@ type Scheduled struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Task struct {
 | 
					type Task struct {
 | 
				
			||||||
	Funcs    []func(app *App)
 | 
					 | 
				
			||||||
	Interval time.Duration
 | 
						Interval time.Duration
 | 
				
			||||||
 | 
						Funcs    []func(app *App)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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{
 | 
				
			||||||
		{Funcs: app.ScheduledTasks.EverySecond, Interval: time.Second},
 | 
							{Interval: time.Second, Funcs: app.ScheduledTasks.EverySecond},
 | 
				
			||||||
		{Funcs: app.ScheduledTasks.EveryMinute, Interval: time.Minute},
 | 
							{Interval: time.Minute, Funcs: app.ScheduledTasks.EveryMinute},
 | 
				
			||||||
		{Funcs: app.ScheduledTasks.EveryHour, Interval: time.Hour},
 | 
							{Interval: time.Hour, Funcs: app.ScheduledTasks.EveryHour},
 | 
				
			||||||
		{Funcs: app.ScheduledTasks.EveryDay, Interval: 24 * time.Hour},
 | 
							{Interval: 24 * time.Hour, Funcs: app.ScheduledTasks.EveryDay},
 | 
				
			||||||
		{Funcs: app.ScheduledTasks.EveryWeek, Interval: 7 * 24 * time.Hour},
 | 
							{Interval: 7 * 24 * time.Hour, Funcs: app.ScheduledTasks.EveryWeek},
 | 
				
			||||||
		{Funcs: app.ScheduledTasks.EveryMonth, Interval: 30 * 24 * time.Hour},
 | 
							{Interval: 30 * 24 * time.Hour, Funcs: app.ScheduledTasks.EveryMonth},
 | 
				
			||||||
		{Funcs: app.ScheduledTasks.EveryYear, Interval: 365 * 24 * time.Hour},
 | 
							{Interval: 365 * 24 * time.Hour, Funcs: app.ScheduledTasks.EveryYear},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var wg sync.WaitGroup
 | 
						var wg sync.WaitGroup
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,54 +1,44 @@
 | 
				
			|||||||
package config
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"flag"
 | 
						"flag"
 | 
				
			||||||
	"log/slog"
 | 
						"github.com/BurntSushi/toml"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Configuration struct {
 | 
					type Configuration struct {
 | 
				
			||||||
	Db struct {
 | 
						Db struct {
 | 
				
			||||||
		Ip          string `json:"DbIp"`
 | 
							Ip          string `toml:"DbIp"`
 | 
				
			||||||
		Port        string `json:"DbPort"`
 | 
							Port        string `toml:"DbPort"`
 | 
				
			||||||
		Name        string `json:"DbName"`
 | 
							Name        string `toml:"DbName"`
 | 
				
			||||||
		User        string `json:"DbUser"`
 | 
							User        string `toml:"DbUser"`
 | 
				
			||||||
		Password    string `json:"DbPassword"`
 | 
							Password    string `toml:"DbPassword"`
 | 
				
			||||||
		AutoMigrate bool   `json:"DbAutoMigrate"`
 | 
							AutoMigrate bool   `toml:"DbAutoMigrate"`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Listen struct {
 | 
						Listen struct {
 | 
				
			||||||
		Ip   string `json:"HttpIp"`
 | 
							Ip   string `toml:"HttpIp"`
 | 
				
			||||||
		Port string `json:"HttpPort"`
 | 
							Port string `toml:"HttpPort"`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Template struct {
 | 
						Template struct {
 | 
				
			||||||
		BaseName    string `json:"BaseTemplateName"`
 | 
							BaseName string `toml:"BaseTemplateName"`
 | 
				
			||||||
		ContentPath string `json:"ContentPath"`
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LoadConfig loads and returns a configuration struct
 | 
					// LoadConfig loads and returns a configuration struct
 | 
				
			||||||
func LoadConfig() Configuration {
 | 
					func LoadConfig() Configuration {
 | 
				
			||||||
	c := flag.String("c", "env.json", "Path to the json configuration file")
 | 
						c := flag.String("c", "env.toml", "Path to the toml configuration file")
 | 
				
			||||||
	flag.Parse()
 | 
						flag.Parse()
 | 
				
			||||||
	file, err := os.Open(*c)
 | 
						file, err := os.ReadFile(*c)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic("unable to open JSON config file: " + err.Error())
 | 
							panic("Unable to read TOML config file: " + err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	defer func(file *os.File) {
 | 
						var Config Configuration
 | 
				
			||||||
		err := file.Close()
 | 
						_, err = toml.Decode(string(file), &Config)
 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			slog.Error("unable to close JSON config file: ", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}(file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	decoder := json.NewDecoder(file)
 | 
					 | 
				
			||||||
	Config := Configuration{}
 | 
					 | 
				
			||||||
	err = decoder.Decode(&Config)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic("unable to decode JSON config file: " + err.Error())
 | 
							panic("Unable to decode TOML config file: " + err.Error())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return Config
 | 
						return Config
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,62 +13,21 @@ type Get struct {
 | 
				
			|||||||
	App *app.App
 | 
						App *app.App
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (g *Get) ShowHome(w http.ResponseWriter, r *http.Request) {
 | 
					func (g *Get) ShowHome(w http.ResponseWriter, _ *http.Request) {
 | 
				
			||||||
	type dataStruct struct {
 | 
						type dataStruct struct {
 | 
				
			||||||
		CsrfToken       string
 | 
							Test 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{
 | 
				
			||||||
		CsrfToken:       CsrfToken,
 | 
							Test: "Hello World!",
 | 
				
			||||||
		Test:            "Hello World!",
 | 
					 | 
				
			||||||
		IsAuthenticated: isAuthenticated,
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	templating.RenderTemplate(w, "templates/pages/home.html", data)
 | 
						templating.RenderTemplate(g.App, 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)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	isAuthenticated := true
 | 
					 | 
				
			||||||
	user, err := models.CurrentUser(g.App, r)
 | 
					 | 
				
			||||||
	if err != nil || user.Id == 0 {
 | 
					 | 
				
			||||||
		isAuthenticated = false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	data := dataStruct{
 | 
					 | 
				
			||||||
		CsrfToken:       CsrfToken,
 | 
					 | 
				
			||||||
		IsAuthenticated: isAuthenticated,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	templating.RenderTemplate(w, "templates/pages/register.html", data)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (g *Get) ShowLogin(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
	type dataStruct struct {
 | 
					 | 
				
			||||||
		CsrfToken       string
 | 
					 | 
				
			||||||
		IsAuthenticated bool
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CsrfToken, err := security.GenerateCsrfToken(w, r)
 | 
						CsrfToken, err := security.GenerateCsrfToken(w, r)
 | 
				
			||||||
@@ -80,5 +39,27 @@ func (g *Get) ShowLogin(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
		CsrfToken: CsrfToken,
 | 
							CsrfToken: CsrfToken,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	templating.RenderTemplate(w, "templates/pages/login.html", data)
 | 
						templating.RenderTemplate(g.App, w, "templates/pages/register.html", data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (g *Get) ShowLogin(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						type dataStruct struct {
 | 
				
			||||||
 | 
							CsrfToken string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CsrfToken, err := security.GenerateCsrfToken(w, r)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data := dataStruct{
 | 
				
			||||||
 | 
							CsrfToken: CsrfToken,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						templating.RenderTemplate(g.App, 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,8 +50,3 @@ 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,8 +9,7 @@ import (
 | 
				
			|||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Migrate given a dummy object of any type, it will create a table with the same name
 | 
					// 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
 | 
				
			||||||
// 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()
 | 
				
			||||||
@@ -24,15 +23,10 @@ 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" {
 | 
				
			||||||
		// Create column if dummy for migration is NOT zero value
 | 
								err := createColumn(app, tableName, fieldName, fieldType.Type.Name())
 | 
				
			||||||
		fieldValue := valueOfStruct.Field(i).Interface()
 | 
								if err != nil {
 | 
				
			||||||
		if !reflect.ValueOf(fieldValue).IsZero() {
 | 
									return err
 | 
				
			||||||
			if fieldName != "Id" && fieldName != "id" {
 | 
					 | 
				
			||||||
				err := createColumn(app, tableName, fieldName, fieldType.Type.Name())
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "Db": {
 | 
					 | 
				
			||||||
    "DbIp": "127.0.0.1",
 | 
					 | 
				
			||||||
    "DbPort": "5432",
 | 
					 | 
				
			||||||
    "DbName": "database",
 | 
					 | 
				
			||||||
    "DbUser": "user",
 | 
					 | 
				
			||||||
    "DbPassword": "password",
 | 
					 | 
				
			||||||
    "DbAutoMigrate": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "Listen": {
 | 
					 | 
				
			||||||
    "HttpIp": "127.0.0.1",
 | 
					 | 
				
			||||||
    "HttpPort": "8090"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "Template": {
 | 
					 | 
				
			||||||
    "BaseTemplateName": "templates/base.html",
 | 
					 | 
				
			||||||
    "ContentPath": "templates"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										14
									
								
								env_example.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								env_example.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					[Db]
 | 
				
			||||||
 | 
					DbIp = "127.0.0.1"
 | 
				
			||||||
 | 
					DbPort = "5432"
 | 
				
			||||||
 | 
					DbName = "test"
 | 
				
			||||||
 | 
					DbUser = "postgres"
 | 
				
			||||||
 | 
					DbPassword = "postgres"
 | 
				
			||||||
 | 
					DbAutoMigrate = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Listen]
 | 
				
			||||||
 | 
					HttpIp = "127.0.0.1"
 | 
				
			||||||
 | 
					HttpPort = "8090"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Template]
 | 
				
			||||||
 | 
					BaseTemplateName = "templates/base.html"
 | 
				
			||||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							@@ -1,8 +1,10 @@
 | 
				
			|||||||
module GoWeb
 | 
					module GoWeb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go 1.22
 | 
					go 1.21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/lib/pq v1.10.9
 | 
						github.com/lib/pq v1.10.9
 | 
				
			||||||
	golang.org/x/crypto v0.19.0
 | 
						golang.org/x/crypto v0.13.0
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require github.com/BurntSushi/toml v1.3.2
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
								
							@@ -1,4 +1,6 @@
 | 
				
			|||||||
 | 
					github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
 | 
				
			||||||
 | 
					github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
				
			||||||
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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
 | 
					golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
 | 
				
			||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 | 
					golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								main.go
									
									
									
									
									
								
							@@ -6,7 +6,6 @@ import (
 | 
				
			|||||||
	"GoWeb/database"
 | 
						"GoWeb/database"
 | 
				
			||||||
	"GoWeb/models"
 | 
						"GoWeb/models"
 | 
				
			||||||
	"GoWeb/routes"
 | 
						"GoWeb/routes"
 | 
				
			||||||
	"GoWeb/templating"
 | 
					 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"embed"
 | 
						"embed"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
@@ -68,13 +67,6 @@ 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: true, // Booleans must be true to migrate properly
 | 
							RememberMe: false,
 | 
				
			||||||
		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 SessionByAuthToken(app *app.App, authToken string) (Session, error) {
 | 
					func GetSessionByAuthToken(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,8 +2,6 @@ package models
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"GoWeb/app"
 | 
						"GoWeb/app"
 | 
				
			||||||
	"crypto/sha256"
 | 
					 | 
				
			||||||
	"encoding/hex"
 | 
					 | 
				
			||||||
	"log/slog"
 | 
						"log/slog"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -29,23 +27,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\""
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CurrentUser finds the currently logged-in user by session cookie
 | 
					// GetCurrentUser finds the currently logged-in user by session cookie
 | 
				
			||||||
func CurrentUser(app *app.App, r *http.Request) (User, error) {
 | 
					func GetCurrentUser(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 := SessionByAuthToken(app, cookie.Value)
 | 
						session, err := GetSessionByAuthToken(app, cookie.Value)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return User{}, err
 | 
							return User{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return UserById(app, session.UserId)
 | 
						return GetUserById(app, session.UserId)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UserById finds a User table row in the database by id and returns a struct representing this row
 | 
					// GetUserById finds a User table row in the database by id and returns a struct representing this row
 | 
				
			||||||
func UserById(app *app.App, id int64) (User, error) {
 | 
					func GetUserById(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)
 | 
				
			||||||
@@ -56,8 +54,8 @@ func UserById(app *app.App, id int64) (User, error) {
 | 
				
			|||||||
	return user, nil
 | 
						return user, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UserByUsername finds a User table row in the database by username and returns a struct representing this row
 | 
					// GetUserByUsername finds a User table row in the database by username and returns a struct representing this row
 | 
				
			||||||
func UserByUsername(app *app.App, username string) (User, error) {
 | 
					func GetUserByUsername(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)
 | 
				
			||||||
@@ -70,12 +68,7 @@ func UserByUsername(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) {
 | 
				
			||||||
	// Get sha256 hash of password then get bcrypt hash to store
 | 
						hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
 | 
				
			||||||
	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
 | 
				
			||||||
@@ -89,7 +82,7 @@ func CreateUser(app *app.App, username string, password string, createdAt time.T
 | 
				
			|||||||
		return User{}, err
 | 
							return User{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return UserById(app, lastInsertId)
 | 
						return GetUserById(app, lastInsertId)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AuthenticateUser validates the password for the specified user
 | 
					// AuthenticateUser validates the password for the specified user
 | 
				
			||||||
@@ -102,12 +95,7 @@ func AuthenticateUser(app *app.App, w http.ResponseWriter, username string, pass
 | 
				
			|||||||
		return Session{}, err
 | 
							return Session{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get sha256 hash of password then check bcrypt
 | 
						err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
 | 
				
			||||||
	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,11 +22,12 @@ func Get(app *app.App) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	staticHandler := http.FileServer(http.FS(staticFS))
 | 
						staticHandler := http.FileServer(http.FS(staticFS))
 | 
				
			||||||
	http.Handle("GET /static/", http.StripPrefix("/static/", staticHandler))
 | 
						http.Handle("/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("GET /", getController.ShowHome)
 | 
						http.HandleFunc("/", getController.ShowHome)
 | 
				
			||||||
	http.HandleFunc("GET /login", getController.ShowLogin)
 | 
						http.HandleFunc("/login", getController.ShowLogin)
 | 
				
			||||||
	http.HandleFunc("GET /register", getController.ShowRegister)
 | 
						http.HandleFunc("/register", getController.ShowRegister)
 | 
				
			||||||
 | 
						http.HandleFunc("/logout", getController.Logout)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,6 @@ func Post(app *app.App) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// User authentication
 | 
						// User authentication
 | 
				
			||||||
	http.HandleFunc("POST /register-handle", middleware.Csrf(postController.Register))
 | 
						http.HandleFunc("/register-handle", middleware.Csrf(postController.Register))
 | 
				
			||||||
	http.HandleFunc("POST /login-handle", middleware.Csrf(postController.Login))
 | 
						http.HandleFunc("/login-handle", middleware.Csrf(postController.Login))
 | 
				
			||||||
	http.HandleFunc("POST /logout", middleware.Csrf(postController.Logout))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,24 +3,9 @@
 | 
				
			|||||||
<head>
 | 
					<head>
 | 
				
			||||||
    <meta charset="UTF-8">
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
    <title>SiteName - {{ template "pageTitle" }}</title>
 | 
					    <title>SiteName - {{ template "pageTitle" }}</title>
 | 
				
			||||||
    <link href="/static/css/style.css" rel="stylesheet">
 | 
					    <link rel="stylesheet" href="/static/css/style.css">
 | 
				
			||||||
</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" placeholder="John" type="text"><br><br>
 | 
					        <input id="username" name="username" type="text" placeholder="John"><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" name="remember" type="checkbox"><br><br>
 | 
					        <input id="remember" type="checkbox" name="remember"><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" placeholder="John" type="text"><br><br>
 | 
					        <input id="username" name="username" type="text" placeholder="John"><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,82 +2,45 @@ package templating
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"GoWeb/app"
 | 
						"GoWeb/app"
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
	"io/fs"
 | 
					 | 
				
			||||||
	"log/slog"
 | 
						"log/slog"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var templates = make(map[string]*template.Template) // This is only used here, does not need to be in app.App
 | 
					// RenderTemplate renders and serves a template from the embedded filesystem optionally with given data
 | 
				
			||||||
 | 
					func RenderTemplate(app *app.App, w http.ResponseWriter, contentPath string, data any) {
 | 
				
			||||||
 | 
						templatePath := app.Config.Template.BaseName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func BuildPages(app *app.App) error {
 | 
						templateContent, err := app.Res.ReadFile(templatePath)
 | 
				
			||||||
	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, "Template not found", 404)
 | 
							http.Error(w, err.Error(), 500)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := t.Execute(w, data) // Execute prebuilt template with dynamic data
 | 
						t, err := template.New(templatePath).Parse(string(templateContent))
 | 
				
			||||||
 | 
						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