2026-02-27 23:39:08 +00:00
|
|
|
package cmds
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"html"
|
|
|
|
|
"html/template"
|
|
|
|
|
"log"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"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"
|
|
|
|
|
"lmika.dev/lmika/weiro/config"
|
|
|
|
|
"lmika.dev/lmika/weiro/handlers"
|
|
|
|
|
"lmika.dev/lmika/weiro/handlers/middleware"
|
2026-03-04 11:33:39 +00:00
|
|
|
"lmika.dev/lmika/weiro/models"
|
|
|
|
|
"lmika.dev/lmika/weiro/providers/markdown"
|
2026-02-27 23:39:08 +00:00
|
|
|
"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 }
|
2026-03-04 11:33:39 +00:00
|
|
|
fiberTemplate.Funcmap["markdown"] = func() func(s string, site models.Site) template.HTML {
|
|
|
|
|
mdParser := markdown.NewRendererForUI()
|
|
|
|
|
return func(s string, site models.Site) template.HTML {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
if site.ID != 0 {
|
|
|
|
|
ctx = models.WithSite(ctx, site)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s, err := mdParser.Render(ctx, s)
|
|
|
|
|
if err != nil {
|
2026-02-27 23:39:08 +00:00
|
|
|
return template.HTML("Markdown error: " + html.EscapeString(err.Error()))
|
|
|
|
|
}
|
2026-03-04 11:33:39 +00:00
|
|
|
return template.HTML(s)
|
2026-02-27 23:39:08 +00:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// 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}
|
2026-02-27 23:39:08 +00:00
|
|
|
|
|
|
|
|
app.Get("/login", lh.Login)
|
|
|
|
|
app.Post("/login", lh.DoLogin)
|
|
|
|
|
app.Post("/logout", lh.Logout)
|
|
|
|
|
|
2026-03-05 11:37:11 +00:00
|
|
|
siteGroup := app.Group("/sites/:siteID", middleware.LogErrors(), middleware.RequireUser(svcs.Auth), middleware.RequiresSite(svcs.Sites))
|
2026-02-27 23:39:08 +00:00
|
|
|
|
|
|
|
|
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)
|
2026-03-04 11:33:39 +00:00
|
|
|
siteGroup.Get("/uploads/slug/+", uh.ShowFromSlug)
|
2026-03-02 11:26:40 +00:00
|
|
|
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)
|
2026-03-04 11:33:39 +00:00
|
|
|
siteGroup.Delete("/uploads/:uploadID", uh.Delete)
|
2026-03-02 09:48:41 +00:00
|
|
|
|
2026-02-27 23:39:08 +00:00
|
|
|
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
|
|
|
|
|
}
|