Files
go_blog/main.go
T
kevinandClaude Fable 5 e872c08f3b feat: article attachments with content-addressed dedup
Add an attachments table and AJAX upload/management on the article
create and edit pages.

- Attachment model with article_id (0 while pending on create),
  session_token ownership, and SHA-256 content-addressed stored_name
- Upload validates via the platform policy (switch/type/size) and
  deduplicates on disk by content hash
- Plan-A binding: attachments uploaded before an article exists are
  owned by a session token and bound to the new article on save
- Delete soft-removes the record and drops the disk file only when no
  remaining rows reference it (reference counting for deduped files)
- Edit page loads existing attachments via JSON list endpoint
- Row actions: insert into body (markdown image/link), set as cover
  (image only), and delete
- Download URLs use the configured default download base URL when set,
  else the local /uploads path

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-21 21:02:09 +08:00

108 lines
3.3 KiB
Go

package main
import (
"fmt"
"log"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"go_blog/config"
"go_blog/handlers"
"go_blog/middleware"
"go_blog/models"
)
func main() {
// 1. Load configuration (auto-creates if missing).
cfg := config.LoadConfig()
// 2. Initialize the database (auto-migrates, seeds admin).
db := models.InitDB(cfg)
// 2b. Warm the platform configuration cache from the database.
models.LoadConfigCache(db)
// 3. Create session store (cookie-based).
store := cookie.NewStore([]byte(cfg.Secret))
store.Options(sessions.Options{
Path: "/",
MaxAge: 86400, // 24 hours
HttpOnly: true, // prevent XSS access
Secure: false, // set true in production with HTTPS
})
// 4. Create Gin router.
router := gin.Default()
// 5. Load HTML templates.
router.LoadHTMLGlob("templates/**/*.html")
// 6. Serve uploaded files (avatars etc.) from the storage path.
router.Static("/uploads", cfg.Path)
// 6. Global session middleware.
router.Use(sessions.Sessions("blog_session", store))
// 7. Global context middleware (sets IsLoggedIn, Username for templates).
router.Use(middleware.SetUserContext(db))
// 8. Register routes.
router.GET("/", handlers.HomePage(db))
router.GET("/login", handlers.LoginPage())
router.POST("/login", handlers.Login(db))
router.POST("/logout", handlers.Logout())
router.GET("/article/:slug", handlers.ArticleDetail(db))
// Protected admin routes.
admin := router.Group("/admin")
admin.Use(middleware.AuthRequired())
{
admin.GET("", handlers.AdminDashboard(db))
admin.GET("/articles", handlers.ArticleListPage(db))
admin.GET("/articles/new", handlers.ArticleCreatePage(db))
admin.POST("/articles/new", handlers.ArticleCreate(db))
admin.GET("/articles/:id/edit", handlers.ArticleEditPage(db))
admin.POST("/articles/:id/edit", handlers.ArticleUpdate(db))
admin.POST("/articles/:id/delete", handlers.ArticleDelete(db))
}
// Protected article attachment routes (AJAX uploads / management).
attachments := router.Group("/admin/articles")
attachments.Use(middleware.AuthRequired())
{
attachments.POST("/attachments", handlers.UploadAttachment(db, cfg.Path))
attachments.POST("/attachments/:id/delete", handlers.DeleteAttachment(db, cfg.Path))
attachments.GET("/:id/attachments", handlers.ListAttachments(db))
}
// Protected admin settings routes (platform configuration).
settings := router.Group("/admin/settings")
settings.Use(middleware.AuthRequired())
{
settings.GET("/site", handlers.SiteSettingsPage(db))
settings.POST("/site", handlers.SiteSettingsSave(db, cfg.Path))
settings.GET("/upload", handlers.UploadSettingsPage(db))
settings.POST("/upload", handlers.UploadSettingsSave(db))
settings.GET("/download", handlers.DownloadSettingsPage(db))
settings.POST("/download", handlers.DownloadSettingsSave(db))
}
// Protected profile routes.
profile := router.Group("/profile")
profile.Use(middleware.AuthRequired())
{
profile.GET("", handlers.ProfilePage(db))
profile.POST("", handlers.UpdateProfile(db, cfg.Path))
profile.POST("/avatar", handlers.UploadAvatar(db, cfg.Path))
}
// 9. Start the server.
addr := fmt.Sprintf(":%s", cfg.Port)
log.Printf("Go Blog starting on http://localhost%s", addr)
if err := router.Run(addr); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}