Tag: {tag}
diff --git a/frontend/src/pages/user_page.tsx b/frontend/src/pages/user_page.tsx
index 4e87dce..23eb5f5 100644
--- a/frontend/src/pages/user_page.tsx
+++ b/frontend/src/pages/user_page.tsx
@@ -28,6 +28,10 @@ export default function UserPage() {
});
}, [username]);
+ useEffect(() => {
+ document.title = username || "User";
+ }, [username]);
+
if (!user) {
return
diff --git a/go.mod b/go.mod
index f04bcf4..d149ee6 100644
--- a/go.mod
+++ b/go.mod
@@ -28,9 +28,11 @@ require (
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/gofiber/schema v1.3.0 // indirect
github.com/gofiber/utils/v2 v2.0.0-beta.8 // indirect
+ github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
github.com/google/uuid v1.6.0
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
+ github.com/k3a/html2text v1.2.1
github.com/klauspost/compress v1.18.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
diff --git a/go.sum b/go.sum
index 6d6bc44..9d05595 100644
--- a/go.sum
+++ b/go.sum
@@ -20,12 +20,18 @@ github.com/gofiber/utils/v2 v2.0.0-beta.8 h1:ZifwbHZqZO3YJsx1ZhDsWnPjaQ7C0YD20LH
github.com/gofiber/utils/v2 v2.0.0-beta.8/go.mod h1:1lCBo9vEF4RFEtTgWntipnaScJZQiM8rrsYycLZ4n9c=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk=
+github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/k3a/html2text v1.2.1 h1:nvnKgBvBR/myqrwfLuiqecUtaK1lB9hGziIJKatNFVY=
+github.com/k3a/html2text v1.2.1/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -49,6 +55,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
@@ -61,17 +69,22 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
diff --git a/main.go b/main.go
index 6859ef2..6ce29df 100644
--- a/main.go
+++ b/main.go
@@ -28,6 +28,8 @@ func main() {
app.Use(middleware.JwtMiddleware)
+ app.Use(middleware.FrontendMiddleware)
+
if debugMode {
app.Use(cors.New(cors.ConfigDefault))
}
diff --git a/server/config/config.go b/server/config/config.go
index 88110be..1852657 100644
--- a/server/config/config.go
+++ b/server/config/config.go
@@ -22,6 +22,10 @@ type ServerConfig struct {
CloudflareTurnstileSiteKey string `json:"cloudflare_turnstile_site_key"`
// CloudflareTurnstileSecretKey is the secret key for Cloudflare Turnstile.
CloudflareTurnstileSecretKey string `json:"cloudflare_turnstile_secret_key"`
+
+ ServerName string `json:"server_name"`
+
+ ServerDescription string `json:"server_description"`
}
func init() {
@@ -34,6 +38,8 @@ func init() {
AllowRegister: true,
CloudflareTurnstileSiteKey: "",
CloudflareTurnstileSecretKey: "",
+ ServerName: "Nysoure",
+ ServerDescription: "Nysoure is a file sharing service.",
}
} else {
data, err := os.ReadFile(filepath)
@@ -78,3 +84,15 @@ func AllowRegister() bool {
func MaxDownloadsPerDayForSingleIP() int {
return config.MaxDownloadsPerDayForSingleIP
}
+
+func CloudflareTurnstileSiteKey() string {
+ return config.CloudflareTurnstileSiteKey
+}
+
+func ServerName() string {
+ return config.ServerName
+}
+
+func ServerDescription() string {
+ return config.ServerDescription
+}
diff --git a/server/middleware/frontend_middleware.go b/server/middleware/frontend_middleware.go
new file mode 100644
index 0000000..ecf7d11
--- /dev/null
+++ b/server/middleware/frontend_middleware.go
@@ -0,0 +1,106 @@
+package middleware
+
+import (
+ "fmt"
+ "nysoure/server/config"
+ "nysoure/server/service"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/gofiber/fiber/v3"
+ "github.com/gomarkdown/markdown"
+ "github.com/gomarkdown/markdown/html"
+ "github.com/gomarkdown/markdown/parser"
+ "github.com/k3a/html2text"
+)
+
+func FrontendMiddleware(c fiber.Ctx) error {
+ if strings.HasPrefix(c.Path(), "/api") {
+ return c.Next()
+ }
+
+ path := c.Path()
+ file := "static" + path
+
+ if _, err := os.Stat(file); path == "/" || os.IsNotExist(err) {
+ return serveIndexHtml(c)
+ } else {
+ return c.SendFile(file)
+ }
+}
+
+func serveIndexHtml(c fiber.Ctx) error {
+ data, err := os.ReadFile("static/index.html")
+ if err != nil {
+ return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
+ }
+ content := string(data)
+
+ siteName := config.ServerName()
+ description := config.ServerDescription()
+ preview := "/icon-192.png"
+ title := siteName
+ url := c.OriginalURL()
+ cfTurnstileSiteKey := config.CloudflareTurnstileSiteKey()
+
+ if strings.HasPrefix(url, "/resources/") {
+ idStr := strings.TrimPrefix(url, "/resources/")
+ id, err := strconv.Atoi(idStr)
+ if err == nil {
+ r, err := service.GetResource(uint(id))
+ if err == nil {
+ if len(r.Images) > 0 {
+ preview = fmt.Sprintf("/images/%d", r.Images[0].ID)
+ }
+ title = r.Title
+ description = getResourceDescription(r.Article)
+ }
+ }
+ } else if strings.HasPrefix(url, "/user/") {
+ username := strings.TrimPrefix(url, "/user/")
+ u, err := service.GetUserByUsername(username)
+ if err == nil {
+ preview = fmt.Sprintf("/avatar/%d", u.ID)
+ title = u.Username
+ description = "User " + u.Username + "'s profile"
+ }
+ }
+
+ content = strings.ReplaceAll(content, "{{SiteName}}", siteName)
+ content = strings.ReplaceAll(content, "{{Description}}", description)
+ content = strings.ReplaceAll(content, "{{Preview}}", preview)
+ content = strings.ReplaceAll(content, "{{Title}}", title)
+ content = strings.ReplaceAll(content, "{{Url}}", url)
+ content = strings.ReplaceAll(content, "{{CFTurnstileSiteKey}}", cfTurnstileSiteKey)
+
+ c.Set("Content-Type", "text/html; charset=utf-8")
+ return c.SendString(content)
+}
+
+func getResourceDescription(article string) string {
+ htmlContent := mdToHTML([]byte(article))
+ plain := html2text.HTML2Text(string(htmlContent))
+ if len([]rune(plain)) > 100 {
+ plain = string([]rune(plain)[:100])
+ }
+ plain = strings.ReplaceAll(plain, "\n", " ")
+ plain = strings.ReplaceAll(plain, "\r", "")
+ plain = strings.ReplaceAll(plain, "\t", "")
+ plain = strings.TrimSpace(plain)
+ return plain
+}
+
+func mdToHTML(md []byte) []byte {
+ // create Markdown parser with extensions
+ extensions := parser.CommonExtensions | parser.NoEmptyLineBeforeBlock | parser.MathJax
+ p := parser.NewWithExtensions(extensions)
+ doc := p.Parse(md)
+
+ // create HTML renderer with extensions
+ htmlFlags := html.CommonFlags | html.HrefTargetBlank
+ opts := html.RendererOptions{Flags: htmlFlags}
+ renderer := html.NewRenderer(opts)
+
+ return markdown.Render(doc, renderer)
+}