blogadmin

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: AGPL-3.0 Imports: 24 Imported by: 0

README

blogadmin

Tests Status Go Report Card PkgGoDev

License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). You can find a copy of the license at https://www.gnu.org/licenses/agpl-3.0.en.html

For commercial use, please use my contact page to obtain a commercial license.

Introduction

Admin interface for github.com/dracory/blogstore. Provides a ready-to-use admin panel for managing blog posts, categories, tags, SEO, media, post versions, and AI-powered content generation.

Modeled after github.com/dracory/shopadmin — same folder-per-controller pattern, same UiConfig/UiBase conventions.

Features

  • Post management — create, update, delete, list with AJAX
  • Category management — create, update, delete, drag-and-drop reorder
  • Tag management — create, update, delete
  • Post versioning — automatic snapshots on every save, with selective attribute restoration
  • Media management — upload, reorder, delete post images
  • SEO management — slug, canonical URL, meta description, meta keywords, meta robots, old slugs
  • Blog settings — blog-level configuration via AJAX
  • AI tools — title generator, post generator, post editor with section/paragraph regeneration, block-based content editor
  • Multi-filter tags — stack multiple filter conditions as removable badge tags; filter state is shareable via URL
  • Custom layouts — bring your own layout via FuncLayout
  • Bootstrap + Vue CDN — default UI works out of the box

Installation

go get github.com/dracory/blogadmin

Quick Start

package main

import (
    "log/slog"
    "net/http"
    "os"

    "github.com/dracory/blogadmin"
    "github.com/dracory/blogstore"
    "github.com/dracory/customstore"
    "github.com/dracory/settingstore"
)

func main() {
    store, err := blogstore.NewStore(blogstore.NewStoreOptions{
        DB:                 yourDB,
        PostTableName:      "blog_post",
        AutomigrateEnabled: true,
        VersioningEnabled:  true,
        TaxonomyEnabled:    true,
    })
    if err != nil {
        log.Fatal(err)
    }

    customStore, _ := customstore.NewStore(customstore.NewStoreOptions{
        DB:                 yourDB,
        TableName:          "custom_record",
        AutomigrateEnabled: true,
    })

    settingStore, _ := settingstore.NewStore(settingstore.NewStoreOptions{
        DB:                 yourDB,
        SettingTableName:   "setting",
        AutomigrateEnabled: true,
    })

    admin, err := blogadmin.New(blogadmin.AdminOptions{
        Store:        store,
        Logger:       slog.New(slog.NewTextHandler(os.Stderr, nil)),
        CustomStore:  customStore,
        SettingStore: settingStore,
        AdminHomeURL: "/admin",
        BlogAdminURL: "/admin/blog",
    })
    if err != nil {
        log.Fatal(err)
    }

    http.Handle("/admin/blog", http.HandlerFunc(admin.Handle))
    http.ListenAndServe(":8080", nil)
}

See example/ for a complete runnable server with in-memory SQLite and seed data.

Integration with a Router

blogadmin.AdminInterface exposes Handle(w, r), which is an http.HandlerFunc-compatible method. Wire it into any router that accepts standard http.Handler:

// stdlib
mux.Handle("/admin/blog", http.HandlerFunc(admin.Handle))

// github.com/dracory/rtr
route := rtr.NewRoute().
    SetName("Admin > Blog").
    SetPath("/admin/blog").
    SetHTMLHandler(admin.Handle)

AI Features

AI controllers (title generator, post generator, post editor) require an LLM factory. Provide one via LlmFactory:

admin, _ := blogadmin.New(blogadmin.AdminOptions{
    Store:        store,
    Logger:       logger,
    CustomStore:  customStore,
    SettingStore: settingStore,
    LlmFactory: func() (llm.LlmInterface, error) {
        return llm.NewLlm(llm.LlmOptions{
            Provider: llm.PROVIDER_OPENAI,
            ApiKey:   os.Getenv("OPENAI_API_KEY"),
            Model:    "gpt-4o",
        })
    },
})

If LlmFactory is nil, AI controllers return an error to the user instead of panicking. CustomStore and SettingStore are also required for AI controllers — nil means they return an error.

Custom Layout

By default, blogadmin renders a bare-bones HTML page with Bootstrap and Vue from CDN. To embed the admin inside your own layout (branding, menus, etc.), provide FuncLayout:

admin, _ := blogadmin.New(blogadmin.AdminOptions{
    Store:  store,
    Logger: logger,
    FuncLayout: func(w http.ResponseWriter, r *http.Request, title, body string, opts struct {
        Styles     []string
        StyleURLs  []string
        Scripts    []string
        ScriptURLs []string
    }) string {
        return myLayout(w, r, title, body, opts)
    },
})

FuncLayout receives the request and response writer so the host project can access request context (auth user, locale, etc.) when rendering the layout.

Testing

go test ./...

Tests use an in-memory SQLite database via modernc.org/sqlite — no external services required.

Documentation

Overview

Package blogadmin provides a standalone blog admin interface following the folder-per-controller pattern. Each controller is in its own subfolder and handles its own views and AJAX data.

This module is modeled on github.com/dracory/shopadmin.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrStoreRequired is returned when Store is not provided
	ErrStoreRequired = errors.New("blog store is required")

	// ErrLoggerRequired is returned when Logger is not provided
	ErrLoggerRequired = errors.New("logger is required")
)

Common errors

Functions

This section is empty.

Types

type AdminInterface

type AdminInterface interface {
	Handle(w http.ResponseWriter, r *http.Request)
}

AdminInterface defines the interface for the blog admin

func New

func New(opts AdminOptions) (AdminInterface, error)

New creates a new blog admin instance. Returns ErrStoreRequired if Store is nil, ErrLoggerRequired if Logger is nil.

type AdminOptions

type AdminOptions struct {
	// Store is the blogstore.StoreInterface (required)
	Store blogstore.StoreInterface

	// Logger is required
	Logger *slog.Logger

	// CustomStore is required for AI controllers (ai_title_generator,
	// ai_post_generator, ai_post_editor). Nil means AI controllers
	// that need it return an error to the user.
	CustomStore customstore.StoreInterface

	// SettingStore is required for ai_title_generator. Nil means the
	// title generator returns an error when reading settings.
	SettingStore settingstore.StoreInterface

	// LlmFactory creates an LLM engine instance. Required for all AI
	// controllers. Nil means AI controllers return an error to the user.
	LlmFactory shared.LlmFactoryFunc

	// FuncLayout is an optional function to render the admin interface
	// inside your own layout (branding, menus, etc.). It receives the
	// request and response writer so the host project can access request
	// context (auth user, locale, etc.) when rendering the layout.
	FuncLayout func(w http.ResponseWriter, r *http.Request, title string, body string, options struct {
		Styles     []string
		StyleURLs  []string
		Scripts    []string
		ScriptURLs []string
	}) string

	// AdminHomeURL is the URL for the admin home page (default: "/admin")
	AdminHomeURL string

	// BlogAdminURL is the base URL for blog admin (default: "/admin/blog")
	BlogAdminURL string

	// FileManagerURL is the URL for the file manager (optional)
	FileManagerURL string

	// AuthUserID returns the authenticated user ID from the request.
	// If it returns "", the user is treated as unauthenticated.
	AuthUserID func(r *http.Request) string
}

AdminOptions contains all dependencies and configuration for the blog admin.

Store and Logger are required. CustomStore, SettingStore, and LlmFactory are required only for the AI controllers; if nil, AI controllers return an error to the user instead of panicking.

FuncLayout is an optional function to render the admin interface inside your own layout (branding, menus, etc.). If nil, a default bare-bones HTML page is used (Bootstrap + Vue CDN). Uses anonymous struct to match shopadmin exactly, so consumers can reuse their shopadmin layout function for blogadmin.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL