asset caching & image re-encode and downscale
This commit is contained in:
BIN
assets/leaves.webp
Normal file
BIN
assets/leaves.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
2
go.mod
2
go.mod
@@ -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
4
go.sum
@@ -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
96
main.go
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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 |
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user