Initial commit

This commit is contained in:
Hugo Mårdbrink 2025-02-11 18:26:30 +01:00
commit 7cbcab0d48
25 changed files with 25504 additions and 0 deletions

50
internal/auth/auth.go Normal file
View file

@ -0,0 +1,50 @@
package auth
import (
"os"
"crypto/sha512"
"crypto/subtle"
"encoding/hex"
"net/http"
"log"
"github.com/labstack/echo/v4"
)
const (
envUsernameHash = "HUGOMARDBRINK_USERNAME"
envPasswordHash = "HUGOMARDBRINK_PASSWORD"
)
func BasicAuth(next echo.HandlerFunc) echo.HandlerFunc {
return echo.HandlerFunc(func(c echo.Context) error {
username, password, ok := c.Request().BasicAuth()
if !ok {
c.Response().Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
usernameHash := sha512.Sum512([]byte(username))
passwordHash := sha512.Sum512([]byte(password))
expectedUsernameHash, err := hex.DecodeString(os.Getenv(envUsernameHash))
if err != nil {
log.Fatal(err)
}
expectedPasswordHash, err := hex.DecodeString(os.Getenv(envPasswordHash))
if err != nil {
log.Fatal(err)
}
usernameMatch := subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1
passwordMatch := subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:]) == 1
if usernameMatch && passwordMatch {
return next(c)
} else {
c.Response().Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
})
}

View file

@ -0,0 +1,6 @@
package config
const (
DatabaseFile = "./database/data.db"
SeedFile = "./database/seed.sql"
)

View file

@ -0,0 +1,67 @@
package handlers
import (
"database/sql"
"net/http"
"github.com/labstack/echo/v4"
"hugo.mardbrink.se/internal/models"
)
func RegisterRoutes(e *echo.Echo, db *sql.DB) {
e.GET("/", homePageHandler())
e.GET("/projects", projectsPageHandler())
e.GET("/articles", articlesPageHandler())
}
func homePageHandler() echo.HandlerFunc {
return func(c echo.Context) error {
isHtmx := c.Request().Header.Get("HX-Request") == "true"
c.Request().Header.Add("Vary", "HX-Request")
p := models.Page{
Title: "Hugo Mårdbrink",
Description: "Home page of Hugo Mårdbrinks personal website.",
IsHtmx: isHtmx,
Breadcrumbs: models.Breadcrumbs{models.NewBreadcrumb("home", "/")}}
hp := models.NewHomePage(p)
return c.Render(http.StatusOK, "home", hp)
}
}
func projectsPageHandler() echo.HandlerFunc {
return func(c echo.Context) error {
isHtmx := c.Request().Header.Get("HX-Request") == "true"
c.Request().Header.Add("Vary", "HX-Request")
p := models.Page{
Title: "Hugo Mårdbrink - Projects",
Description: "Hobby projects by Hugo Mårdbrink.",
IsHtmx: isHtmx,
Breadcrumbs: models.Breadcrumbs{
models.NewBreadcrumb("Home", "/"),
models.NewBreadcrumb("projects", "/projects")}}
pp := models.NewProjectPage(p)
return c.Render(http.StatusOK, "projects", pp)
}
}
func articlesPageHandler() echo.HandlerFunc {
return func(c echo.Context) error {
isHtmx := c.Request().Header.Get("HX-Request") == "true"
c.Request().Header.Add("Vary", "HX-Request")
p := models.Page{
Title: "Hugo Mårdbrink - Articles",
Description: "Articles by Hugo Mårdbrink.",
IsHtmx: isHtmx,
Breadcrumbs: models.Breadcrumbs{
models.NewBreadcrumb("Home", "/"),
models.NewBreadcrumb("articles", "/articles")}}
pp := models.NewProjectPage(p)
return c.Render(http.StatusOK, "articles", pp)
}
}

53
internal/models/pages.go Normal file
View file

@ -0,0 +1,53 @@
package models
type Breadcrumb struct {
Title string
Route string
}
type Breadcrumbs = []Breadcrumb
type Page struct {
Title string
Description string
Breadcrumbs Breadcrumbs
IsHtmx bool
}
type HomePage struct {
Page Page
}
type ProjectPage struct {
Page Page
}
func NewPage(title string, description string, breadcrumbs Breadcrumbs, isHtmx bool) Page {
return Page{
Title: title,
Description: description,
Breadcrumbs: breadcrumbs,
IsHtmx: isHtmx,
}
}
func NewBreadcrumb(title string, route string) Breadcrumb {
return Breadcrumb{
Title: title,
Route: route,
}
}
func NewHomePage(page Page) HomePage {
return HomePage{
Page: page,
}
}
func NewProjectPage(page Page) ProjectPage {
return ProjectPage{
Page: page,
}
}