asset caching & image re-encode and downscale

This commit is contained in:
Zoe
2024-11-12 01:30:24 -06:00
parent 796e889809
commit d23b66dda1
11 changed files with 101 additions and 18 deletions

BIN
assets/leaves.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

2
go.mod
View File

@@ -9,6 +9,7 @@ require (
require ( require (
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/chai2010/webp v1.1.1
github.com/gofiber/fiber/v2 v2.52.5 // indirect github.com/gofiber/fiber/v2 v2.52.5 // indirect
github.com/gofiber/fiber/v3 v3.0.0-beta.3 github.com/gofiber/fiber/v3 v3.0.0-beta.3
github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/template v1.8.3 // indirect
@@ -22,6 +23,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-sqlite3 v1.14.24 github.com/mattn/go-sqlite3 v1.14.24
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.55.0 // indirect github.com/valyala/fasthttp v1.55.0 // indirect

4
go.sum
View File

@@ -1,5 +1,7 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -32,6 +34,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=

96
main.go
View File

@@ -1,10 +1,14 @@
package main package main
import ( import (
"bytes"
"database/sql" "database/sql"
"embed" "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
"image"
"image/jpeg"
"image/png"
"io" "io"
"io/fs" "io/fs"
"log" "log"
@@ -16,6 +20,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/chai2010/webp"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/static" "github.com/gofiber/fiber/v3/middleware/static"
"github.com/gofiber/template/handlebars/v2" "github.com/gofiber/template/handlebars/v2"
@@ -23,13 +28,14 @@ import (
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/juls0730/passport/middleware" "github.com/juls0730/passport/middleware"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"github.com/nfnt/resize"
) )
//go:embed views/** //go:embed views/**
var viewsFS embed.FS var viewsFS embed.FS
//go:embed fonts/** //go:embed assets/**
var fontsFS embed.FS var assetsFS embed.FS
type Category struct { type Category struct {
ID int64 `json:"id"` ID int64 `json:"id"`
@@ -252,13 +258,55 @@ func CreateLink(db *sql.DB) fiber.Handler {
}) })
} }
srcFile, err := file.Open()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Failed to open file",
})
}
defer srcFile.Close()
// Decode the image
var img image.Image
switch contentType {
case "image/jpeg":
img, err = jpeg.Decode(srcFile)
case "image/png":
img, err = png.Decode(srcFile)
default:
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Unsupported image format",
})
}
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Failed to decode image",
})
}
resizedImg := resize.Resize(64, 0, img, resize.Lanczos3)
var buf bytes.Buffer
options := &webp.Options{Lossless: false, Quality: 80}
if err := webp.Encode(&buf, resizedImg, options); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Failed to encode image as WebP",
})
}
assetsDir := "public/uploads" assetsDir := "public/uploads"
ext := filepath.Ext(file.Filename) filename := fmt.Sprintf("%d_%s.webp", time.Now().Unix(), strings.ReplaceAll(req.Name, " ", "_"))
filename := fmt.Sprintf("%d_%s%s", time.Now().Unix(), strings.ReplaceAll(req.Name, " ", "_"), ext)
iconPath := filepath.Join(assetsDir, filename) iconPath := filepath.Join(assetsDir, filename)
if err := c.SaveFile(file, iconPath); err != nil { outFile, err := os.Create(iconPath)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Failed to save file",
})
}
defer outFile.Close()
if _, err := io.Copy(outFile, &buf); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Failed to save file", "message": "Failed to save file",
}) })
@@ -417,10 +465,19 @@ func main() {
Views: engine, Views: engine,
}) })
router.Use("/", static.New("./public")) router.Use("/", static.New("./public", static.Config{
Browse: false,
MaxAge: 31536000,
}))
router.Use("/fonts", static.New("", static.Config{ assetsDir, err := fs.Sub(assetsFS, "assets")
FS: fontsFS, if err != nil {
log.Fatal(err)
}
router.Use("/assets", static.New("", static.Config{
FS: assetsDir,
MaxAge: 31536000,
})) }))
router.Get("/", func(c fiber.Ctx) error { router.Get("/", func(c fiber.Ctx) error {
@@ -442,11 +499,21 @@ func main() {
}, "layouts/main") }, "layouts/main")
}) })
router.Use(middleware.AdminMiddleware(app.db))
router.Get("/admin/login", func(c fiber.Ctx) error { router.Get("/admin/login", func(c fiber.Ctx) error {
if c.Locals("IsAdmin") != nil {
return c.Redirect().To("/admin")
}
return c.Render("admin/login", fiber.Map{}, "layouts/main") return c.Render("admin/login", fiber.Map{}, "layouts/main")
}) })
router.Post("/admin/login", func(c fiber.Ctx) error { router.Post("/admin/login", func(c fiber.Ctx) error {
if c.Locals("IsAdmin") != nil {
return c.Redirect().To("/admin")
}
var loginData struct { var loginData struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
@@ -480,9 +547,11 @@ func main() {
return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Logged in successfully"}) return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Logged in successfully"})
}) })
router.Use(middleware.AdminMiddleware(app.db))
router.Get("/admin", func(c fiber.Ctx) error { router.Get("/admin", func(c fiber.Ctx) error {
if c.Locals("IsAdmin") == nil {
return c.Redirect().To("/admin/login")
}
categories, err := app.GetCategories() categories, err := app.GetCategories()
if err != nil { if err != nil {
return err return err
@@ -495,6 +564,13 @@ func main() {
api := router.Group("/api") api := router.Group("/api")
{ {
api.Use(func(c fiber.Ctx) error {
if c.Locals("IsAdmin") == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Unauthorized"})
}
return c.Next()
})
api.Post("/categories", CreateCategory(app.db)) api.Post("/categories", CreateCategory(app.db))
api.Post("/links", CreateLink(app.db)) api.Post("/links", CreateLink(app.db))

View File

@@ -16,7 +16,7 @@ func AdminMiddleware(db *sql.DB) func(c fiber.Ctx) error {
return func(c fiber.Ctx) error { return func(c fiber.Ctx) error {
sessionToken := c.Cookies("SessionToken") sessionToken := c.Cookies("SessionToken")
if sessionToken == "" { if sessionToken == "" {
return c.Redirect().To("/admin/login") return c.Next()
} }
// Check if session exists // Check if session exists
@@ -27,18 +27,19 @@ func AdminMiddleware(db *sql.DB) func(c fiber.Ctx) error {
WHERE session_id = ? WHERE session_id = ?
`, sessionToken).Scan(&session.SessionID, &session.ExpiresAt) `, sessionToken).Scan(&session.SessionID, &session.ExpiresAt)
if err != nil { if err != nil {
return c.Redirect().To("/admin/login") return c.Next()
} }
sessionExpiry, err := time.Parse("2006-01-02 15:04:05-07:00", session.ExpiresAt) sessionExpiry, err := time.Parse("2006-01-02 15:04:05-07:00", session.ExpiresAt)
if err != nil { if err != nil {
return c.Redirect().To("/admin/login") return c.Next()
} }
if sessionExpiry.Before(time.Now()) { if sessionExpiry.Before(time.Now()) {
return c.Redirect().To("/admin/login") return c.Next()
} }
c.Locals("IsAdmin", true)
return c.Next() return c.Next()
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

View File

@@ -1,6 +1,6 @@
<main class="flex justify-center items-center h-100vh relative bg-[#0E0A0E]"> <main class="flex justify-center items-center h-100vh relative bg-[#0E0A0E]">
<div class="flex bg-[#151316] rounded-xl overflow-hidden"> <div class="flex bg-[#151316] rounded-xl overflow-hidden">
<img src="/leaves.jpg" class="h-96 w-64 object-cover" /> <img src="/assets/leaves.webp" class="h-96 w-64 object-cover" />
<div class="flex flex-col p-4 text-center"> <div class="flex flex-col p-4 text-center">
<h2 class="text-2xl"> <h2 class="text-2xl">
Login Login

View File

@@ -8,21 +8,21 @@
<style> <style>
@font-face { @font-face {
font-family: "Instrument Sans"; font-family: "Instrument Sans";
src: url("/fonts/InstrumentSans-Regular.ttf"); src: url("/assets/fonts/InstrumentSans-Regular.ttf");
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: "Instrument Sans"; font-family: "Instrument Sans";
src: url("/fonts/InstrumentSans-SemiBold.ttf"); src: url("/assets/fonts/InstrumentSans-SemiBold.ttf");
font-weight: 600; font-weight: 600;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: "Instrument Sans"; font-family: "Instrument Sans";
src: url("/fonts/InstrumentSans-Italic.ttf"); src: url("/assets/fonts/InstrumentSans-Italic.ttf");
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
} }