weiro/cmds/server.go

148 lines
4.2 KiB
Go
Raw Normal View History

package cmds
import (
"context"
"html"
"html/template"
"log"
"path/filepath"
"strings"
"time"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/extractors"
"github.com/gofiber/fiber/v3/middleware/session"
"github.com/gofiber/fiber/v3/middleware/static"
"github.com/gofiber/storage/sqlite3/v2"
fiber_html "github.com/gofiber/template/html/v3"
"github.com/gofiber/utils/v2"
"github.com/spf13/cobra"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"lmika.dev/lmika/weiro/config"
"lmika.dev/lmika/weiro/handlers"
"lmika.dev/lmika/weiro/handlers/middleware"
"lmika.dev/lmika/weiro/services"
)
func Root() *cobra.Command {
cmd := &cobra.Command{
Use: "weiro",
Short: "Weiro is a simple blogging platform",
Long: `Weiro is a simple blogging platform.
Starting weiro without any arguments will start the server.
`,
Run: func(cmd *cobra.Command, args []string) {
cfg, err := config.LoadConfig()
if err != nil {
log.Fatal(err)
}
svcs, err := services.New(cfg)
if err != nil {
log.Fatal(err)
}
defer svcs.Close()
svcs.PublisherQueue.Start(context.Background())
fiberTemplate := fiber_html.New("./views", ".html")
fiberTemplate.Funcmap["sub"] = func(x, y int) int { return x - y }
fiberTemplate.Funcmap["markdown"] = func() func(s string) template.HTML {
mdParser := goldmark.New(
goldmark.WithExtensions(extension.GFM),
)
return func(s string) template.HTML {
var sb strings.Builder
if err := mdParser.Convert([]byte(s), &sb); err != nil {
return template.HTML("Markdown error: " + html.EscapeString(err.Error()))
}
return template.HTML(sb.String())
}
}()
// Initialize custom config
store := sqlite3.New(sqlite3.Config{
Database: filepath.Join(cfg.DataDir, "./fiber.db"),
Table: "fiber_storage",
Reset: false,
GCInterval: 10 * time.Second,
MaxOpenConns: 100,
MaxIdleConns: 100,
ConnMaxLifetime: 1 * time.Second,
})
app := fiber.New(fiber.Config{
Views: fiberTemplate,
ViewsLayout: "layouts/main",
PassLocalsToViews: true,
})
app.Use(session.New(session.Config{
// Storage
Storage: store,
// Security
CookieSecure: cfg.IsProd(),
CookieSameSite: "Lax",
// Session Management
IdleTimeout: 24 * time.Hour, // Inactivity timeout
AbsoluteTimeout: 7 * 24 * time.Hour, // Maximum session duration
// Cookie Settings
CookiePath: "/",
CookieDomain: cfg.SiteDomain,
CookieSessionOnly: false, // Persist across browser restarts
// Session ID
Extractor: extractors.FromCookie("__wro-session_id"),
KeyGenerator: utils.SecureToken,
// Error Handling
ErrorHandler: func(c fiber.Ctx, err error) {
log.Printf("Session error: %v", err)
},
}))
ih := handlers.IndexHandler{SiteService: svcs.Sites}
lh := handlers.LoginHandler{Config: cfg, AuthService: svcs.Auth}
ph := handlers.PostsHandler{PostService: svcs.Posts}
2026-03-02 09:48:41 +00:00
uh := handlers.UploadsHandler{UploadsService: svcs.Uploads}
app.Get("/login", lh.Login)
app.Post("/login", lh.DoLogin)
app.Post("/logout", lh.Logout)
siteGroup := app.Group("/sites/:siteID", middleware.RequireUser(svcs.Auth), middleware.RequiresSite(svcs.Sites))
siteGroup.Get("/posts", ph.Index)
siteGroup.Get("/posts/new", ph.New)
siteGroup.Get("/posts/:postID", ph.Edit)
siteGroup.Post("/posts", ph.Update)
siteGroup.Patch("/posts/:postID", ph.Patch)
siteGroup.Delete("/posts/:postID", ph.Delete)
2026-03-02 09:48:41 +00:00
siteGroup.Get("/uploads", uh.Index)
siteGroup.Get("/uploads/:uploadID", uh.Show)
siteGroup.Get("/uploads/:uploadID/raw", uh.ShowRaw)
2026-03-02 09:48:41 +00:00
siteGroup.Post("/uploads/pending", uh.New)
siteGroup.Post("/uploads/pending/:guid", uh.UploadPart)
siteGroup.Post("/uploads/pending/:guid/finalize", uh.UploadComplete)
app.Get("/", middleware.OptionalUser(svcs.Auth), ih.Index)
app.Get("/first-run", ih.FirstRun)
app.Post("/first-run", ih.FirstRunSubmit)
app.Get("/static/*", static.New("./static"))
if err := app.Listen(":3000"); err != nil {
log.Println(err)
}
},
}
cmd.AddCommand(Sites())
cmd.AddCommand(PubTargets())
return cmd
}