pubengine

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Mar 4, 2026 License: MIT Imports: 34 Imported by: 0

README

pubengine

A Go blog publishing framework. Ships blog CRUD, admin dashboard, privacy-first analytics, RSS, and sitemap out of the box. You own the templates, pubengine handles everything else.

Built with Echo, templ, HTMX, Tailwind CSS, and SQLite.

How it works

pubengine is a Go module, not a standalone app. You import it, provide your own templ templates via a ViewFuncs struct, and pubengine wires up all the handlers, middleware, database, caching, and analytics. Think of it like Django for Go blogs: convention over configuration with full template ownership.

+-----------------+       +-------------------+
|  Your Project   |       |    pubengine      |
|                 |       |                   |
|  main.go        |------>|  Handlers         |
|  views/*.templ  |       |  Middleware       |
|  assets/        |       |  Store (SQLite)   |
|  src/           |       |  Cache            |
|  public/        |       |  Analytics        |
|                 |       |  RSS / Sitemap    |
|  ViewFuncs{     |       |  Rate Limiter     |
|    Home: ...,   |       |  Session / CSRF   |
|    Post: ...,   |       |  Markdown         |
|  }              |       |  Image Library    |
+-----------------+       +-------------------+

Quick start

Install the CLI
go install github.com/eringen/pubengine/cmd/pubengine@latest
Scaffold a new project
pubengine new github.com/yourname/myblog
cd myblog

This generates a complete project:

myblog/
├── main.go               # ~40 lines: config + ViewFuncs wiring
├── go.mod
├── views/
│   ├── home.templ        # Home page with blog listing
│   ├── post.templ        # Single post with related posts
│   ├── admin.templ       # Admin login + dashboard + editor
│   ├── nav.templ         # Head, Nav, Footer
│   ├── notfound.templ    # 404 page
│   ├── servererror.templ # 500 page
│   └── helpers.go        # Type aliases for BlogPost, PageMeta
├── assets/
│   └── tailwind.css      # Tailwind directives
├── src/
│   └── app.js            # Custom JavaScript entry point
├── public/
│   ├── robots.txt
│   └── favicon.svg
├── data/                 # SQLite databases (auto created)
├── Makefile
├── package.json
├── tailwind.config.js
└── .env.example
Run it
go mod tidy
npm install
make run

Your blog is running at http://localhost:3000. Admin dashboard at /admin/.

Usage

The main.go pattern

Every pubengine site follows the same structure:

package main

import (
    "log"

    "github.com/eringen/pubengine"
    "myblog/views"
)

func main() {
    app := pubengine.New(
        pubengine.SiteConfig{
            Name:          pubengine.EnvOr("SITE_NAME", "My Blog"),
            URL:           pubengine.EnvOr("SITE_URL", "http://localhost:3000"),
            Description:   pubengine.EnvOr("SITE_DESCRIPTION", "A blog about things"),
            Author:        pubengine.EnvOr("SITE_AUTHOR", "Your Name"),
            Addr:          pubengine.EnvOr("ADDR", ":3000"),
            DatabasePath:  pubengine.EnvOr("DATABASE_PATH", "data/blog.db"),
            AdminPassword: pubengine.MustEnv("ADMIN_PASSWORD"),
            SessionSecret: pubengine.MustEnv("ADMIN_SESSION_SECRET"),
            CookieSecure:  pubengine.EnvOr("COOKIE_SECURE", "") == "true",
        },
        pubengine.ViewFuncs{
            Home:             views.Home,
            HomePartial:      views.HomePartial,
            BlogSection:      views.BlogSection,
            Post:             views.Post,
            PostPartial:      views.PostPartial,
            AdminLogin:       views.AdminLogin,
            AdminDashboard:   views.AdminDashboard,
            AdminFormPartial: views.AdminFormPartial,
            NotFound:         views.NotFound,
            ServerError:      views.ServerError,
        },
    )
    defer app.Close()

    if err := app.Start(); err != nil {
        log.Fatal(err)
    }
}
ViewFuncs

This is the core inversion of control mechanism. You provide templ components, pubengine calls them from its handlers:

type ViewFuncs struct {
    // Full page renders (initial page load)
    Home             func(posts []BlogPost, activeTag string, tags []string, siteURL string) templ.Component
    Post             func(post BlogPost, posts []BlogPost, siteURL string) templ.Component

    // HTMX partial renders (SPA like navigation)
    HomePartial      func(posts []BlogPost, activeTag string, tags []string, siteURL string) templ.Component
    BlogSection      func(posts []BlogPost, activeTag string, tags []string) templ.Component
    PostPartial      func(post BlogPost, posts []BlogPost, siteURL string) templ.Component

    // Admin pages
    AdminLogin       func(showError bool, csrfToken string, googleLoginURL string) templ.Component
    AdminDashboard   func(posts []BlogPost, message string, csrfToken string) templ.Component
    AdminFormPartial func(post BlogPost, csrfToken string) templ.Component
    AdminImages      func(images []Image, csrfToken string) templ.Component

    // Error pages
    NotFound         func() templ.Component
    ServerError      func() templ.Component
}

The framework handles when to call full vs. partial renders based on HTMX headers automatically.

SiteConfig

All configuration in one struct:

Field Type Default Description
Name string "Blog" Site name for nav, footer, RSS, JSON-LD
URL string "http://localhost:3000" Canonical URL for sitemap, RSS, OpenGraph
Description string "" Site description for RSS and meta tags
Author string "" Author name for JSON-LD structured data
Addr string ":3000" Server listen address
DatabasePath string "data/blog.db" SQLite database path
AnalyticsEnabled bool false Enable built in analytics
AnalyticsDatabasePath string "data/analytics.db" Analytics SQLite path
AdminPassword string required Admin login password
SessionSecret string required Session cookie encryption secret
CookieSecure bool false Set true when behind HTTPS
GoogleClientID string "" Google OAuth client ID (optional)
GoogleClientSecret string "" Google OAuth client secret (optional)
GoogleAdminEmail string "" Allowed Google email for admin login (optional)
PostCacheTTL time.Duration 5m In memory post cache TTL
Options

Configure additional behavior with option functions:

// Add custom routes (runs after pubengine's routes)
pubengine.WithCustomRoutes(func(a *pubengine.App) {
    a.Echo.GET("/about/", handleAbout)
    a.Echo.Static("/portfolio", "portfolio")
})

// Change the static assets directory (default: "public")
pubengine.WithStaticDir("static")
Accessing the App

The App struct exposes the underlying components for advanced use:

app := pubengine.New(cfg, views)

app.Config    // SiteConfig
app.Echo      // *echo.Echo, the HTTP server
app.Store     // *Store, SQLite operations
app.Cache     // *PostCache, in memory cache
app.Views     // ViewFuncs

Core types

BlogPost
type BlogPost struct {
    Title     string
    Date      string     // "2024-01-15" format
    Tags      []string
    Summary   string
    Link      string     // "/blog/my-post" (auto generated)
    Slug      string     // "my-post"
    Content   string     // Markdown source
    Published bool
}
PageMeta
type PageMeta struct {
    Title       string   // Page title and og:title
    Description string   // Meta description and og:description
    URL         string   // Canonical URL and og:url
    OGType      string   // "website" or "article"
}

Routes

pubengine registers these routes automatically:

Public
Method Path Description
GET / Home page with blog listing
GET /blog/:slug/ Single blog post
GET /feed.xml RSS feed
GET /sitemap.xml XML sitemap
GET /robots.txt Robots.txt (from static dir)
GET /favicon.svg Favicon (from static dir)
GET /public/* Static assets
Admin
Method Path Description
GET /admin/ Login page or dashboard
POST /admin/login/ Process login
POST /admin/logout/ Logout
GET /admin/post/:slug/ Edit post form (HTMX)
POST /admin/save/ Create or update post
DELETE /admin/post/:slug/ Delete post
GET /admin/images/ Image library (HTMX)
POST /admin/images/upload/ Upload image
DELETE /admin/images/:filename/ Delete image
Analytics (when enabled)
Method Path Description
POST /api/analytics/collect Track page view
GET /admin/analytics/ Analytics dashboard
GET /admin/analytics/api/stats Stats JSON
GET /admin/analytics/fragments/stats Stats HTML fragment
GET /admin/analytics/api/bot-stats Bot stats JSON
GET /admin/analytics/fragments/bot-stats Bot stats HTML fragment

Helper functions

pubengine exports utility functions for use in your templates:

// URL and path helpers
pubengine.BuildURL(base, "blog", slug)     // "https://example.com/blog/my-post/"
pubengine.PathEscape(tag)                   // URL safe tag encoding
pubengine.Slugify("My Post Title")          // "my-post-title"

// Tag helpers
pubengine.JoinTags(tags)                    // "go, web, sqlite"
pubengine.FilterEmpty(tags)                 // Remove empty strings
pubengine.FilterRelatedPosts(current, all)  // Posts sharing tags

// JSON-LD structured data
pubengine.WebsiteJsonLD(cfg)                // WebSite schema
pubengine.BlogPostingJsonLD(post, cfg)      // BlogPosting schema

// Environment helpers (for main.go)
pubengine.EnvOr("KEY", "default")           // Get env var with fallback
pubengine.MustEnv("KEY")                    // Get env var or log.Fatal

// Template rendering
pubengine.Render(c, component)              // Render as HTTP 200
pubengine.RenderStatus(c, 404, component)   // Render with status code

// Auth helpers
pubengine.IsAdmin(c)                        // Check if session is authenticated
pubengine.CsrfToken(c)                      // Extract CSRF token from context

Markdown

pubengine includes a custom markdown renderer (pubengine/markdown package) with no external dependencies.

Supported syntax
Syntax Output
**bold** or __bold__ bold
*italic* or _italic_ italic
`code` Inline code
# Heading 1 <h1>
## Heading 2 <h2>
### Heading 3 <h3>
[text](url) Link (same tab)
[text](url)^ Link (new tab, adds target="_blank")
![alt](url){style} Image with inline CSS
![alt](url){style|w|h} Image with dimensions
- item Unordered list
1. item Ordered list
> quote Blockquote
``` Code block
```lang Code block with language badge
|col|col| Table
--- Horizontal rule
Usage in templates
import "github.com/eringen/pubengine/markdown"

// In a templ component:
@markdown.Markdown(post.Content)
Programmatic usage
import "github.com/eringen/pubengine/markdown"

var buf bytes.Buffer
markdown.RenderMarkdown(&buf, "**hello** world")
// buf.String() == "<p><strong>hello</strong> world\n</p>"
Security

All text is HTML escaped before formatting. Only http, https, mailto, and tel URL schemes are allowed. Bold/italic regex runs only on text outside HTML tags to prevent URL corruption. First image gets fetchpriority="high" for LCP optimization. Inline code content is protected from bold/italic formatting.

Analytics

pubengine includes a built in, privacy first analytics system. No cookies, no third party scripts, no personal data stored.

How it works

IP addresses are hashed with a salted SHA-256 (salt rotates, stored in DB). Visitor IDs are derived from IP + User Agent hash (no cookies). Bot traffic is detected and tracked separately. The system respects Do Not Track (DNT) headers. Data retention is configurable with automatic cleanup (default: 365 days). All data stays in your SQLite database.

Enabling analytics
pubengine.SiteConfig{
    AnalyticsEnabled:      true,
    AnalyticsDatabasePath: "data/analytics.db",
    // ...
}
Client side tracking

The framework ships analytics.js as an embedded asset, automatically served at /public/analytics.js. Include it in your template <head>:

<script src="/public/analytics.js" defer></script>

The script tracks page views, time on page, and handles HTMX navigation. It uses navigator.sendBeacon for reliable unload tracking.

Dashboard

The analytics dashboard is available at /admin/analytics/ (requires admin login). The admin nav bar includes a link to it. It shows:

  • Realtime visitors (last 5 minutes)
  • Unique visitors and total page views
  • Average time on page
  • Top pages and latest visits (last 10)
  • Browser, OS, and device breakdown
  • Referrer sources
  • Daily/hourly/monthly view charts
  • Bot traffic (separate tab with independent period selection)

The dashboard is fully self contained. Its CSS (admin.css) and JS (dashboard.min.js) are embedded in the binary alongside htmx.min.js.

Rate limiting

The analytics collect endpoint is rate limited to 60 requests per IP per minute to prevent flooding.

Google OAuth login

pubengine supports an optional Google OAuth login for the admin panel. When configured, a "Sign in with Google" button appears on the login page alongside the password form. Password login always remains available as a fallback.

Setup
  1. Create OAuth credentials in the Google Cloud Console
  2. Set the authorized redirect URI to https://yourdomain.com/admin/auth/google/callback
  3. Set the environment variables:
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
[email protected]

Or in your SiteConfig:

pubengine.SiteConfig{
    GoogleClientID:     pubengine.EnvOr("GOOGLE_CLIENT_ID", ""),
    GoogleClientSecret: pubengine.EnvOr("GOOGLE_CLIENT_SECRET", ""),
    GoogleAdminEmail:   pubengine.EnvOr("GOOGLE_ADMIN_EMAIL", ""),
    // ...
}

All three fields must be set for Google login to be enabled. Only the email matching GOOGLE_ADMIN_EMAIL (case-insensitive) is allowed to log in.

Middleware

pubengine configures a production ready middleware stack:

  1. NonWWWRedirect redirects www. to bare domain
  2. RequestLogger logs method, URI, status code, latency
  3. Recover provides panic recovery with error logging
  4. Security headers include CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
  5. Session uses cookie based sessions (gorilla/sessions, 12 hour expiry)
  6. CSRF provides token based protection (skipped for analytics endpoint)
  7. Trailing slash enforces consistent URL format
  8. Cache-Control sets static assets to 1 year immutable, pages to 1 hour, admin to no-store

Database

Blog database

SQLite at data/blog.db (auto created on first run).

CREATE TABLE posts (
    slug TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    date TEXT NOT NULL,
    tags TEXT NOT NULL,          -- comma delimited: ",go,web,"
    summary TEXT NOT NULL,
    content TEXT NOT NULL,
    published INTEGER NOT NULL DEFAULT 1
);
Analytics database

Separate SQLite at data/analytics.db.

CREATE TABLE visits (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    visitor_id TEXT NOT NULL,
    session_id TEXT NOT NULL,
    ip_hash TEXT NOT NULL,
    browser TEXT NOT NULL,
    os TEXT NOT NULL,
    device TEXT NOT NULL,
    path TEXT NOT NULL,
    referrer TEXT,
    screen_size TEXT,
    timestamp DATETIME NOT NULL,
    duration_sec INTEGER DEFAULT 0
);

CREATE TABLE bot_visits (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    bot_name TEXT NOT NULL,
    ip_hash TEXT NOT NULL,
    user_agent TEXT NOT NULL,
    path TEXT NOT NULL,
    timestamp DATETIME NOT NULL
);

CREATE TABLE settings (
    key TEXT PRIMARY KEY,
    value TEXT NOT NULL
);

Both databases use WAL mode with tuned pragmas (busy_timeout, synchronous=NORMAL, 8MB cache, 256MB mmap) for concurrent read performance.

Store API

The Store provides all blog CRUD operations:

store, err := pubengine.NewStore("data/blog.db")
defer store.Close()

// Published posts (for public pages)
posts, _ := store.ListPosts("")          // all published, newest first
posts, _ := store.ListPosts("go")        // filtered by tag (case insensitive)
post, _  := store.GetPost("my-slug")     // single published post
tags, _  := store.ListTags()             // unique tags from published posts

// All posts (for admin)
posts, _ := store.ListAllPosts()          // including drafts
post, _  := store.GetPostAny("my-slug")  // regardless of published status

// Write operations
store.SavePost(post)                      // insert or replace
store.DeletePost("my-slug")              // delete by slug

Cache API

The PostCache wraps the store with an in memory cache:

cache := pubengine.NewPostCache(store, 5*time.Minute)

posts, _ := cache.ListPosts("")     // from cache if fresh, else DB
tags, _  := cache.ListTags()        // from cache
post, _  := cache.GetPost("slug")   // from cached post list

cache.Invalidate()                  // clear on write operations

Project structure

pubengine/
├── pubengine.go           # App struct, New(), Start(), Close()
├── config.go              # SiteConfig, Option functions
├── types.go               # BlogPost, PageMeta, Image
├── store.go               # SQLite blog CRUD
├── cache.go               # In memory post cache
├── handlers.go            # Blog handlers (home, post, feed, sitemap)
├── admin.go               # Admin handlers (login, save, delete, images)
├── middleware.go           # Security headers, sessions, CSRF, cache
├── render.go              # Render helpers
├── helpers.go             # Slugify, BuildURL, JSON-LD, tag utils
├── images.go              # Image upload, resize, library
├── limiter.go             # Login rate limiter
├── rss.go                 # RSS XML generation
├── sitemap.go             # Sitemap XML generation
├── embed.go               # Embedded static assets
├── embedded/
│   ├── htmx.min.js        # HTMX library
│   ├── analytics.js       # Client side tracking script
│   ├── dashboard.min.js   # Analytics dashboard JS
│   └── admin.css          # Analytics dashboard styles
├── markdown/
│   ├── markdown.go        # Custom markdown renderer
│   └── markdown_test.go
├── analytics/
│   ├── analytics.go       # IP hashing, UA parsing, bot detection
│   ├── store.go           # Analytics SQLite operations
│   ├── handlers.go        # Collection + dashboard handlers
│   ├── limiter.go         # Analytics rate limiter
│   ├── sqlcgen/           # Generated SQL (sqlc)
│   └── templates/         # Analytics dashboard templ templates
├── scaffold/
│   ├── scaffold.go        # embed.FS for templates
│   └── templates/         # Project scaffolding templates
├── cmd/pubengine/
│   ├── main.go            # CLI entry point
│   └── new.go             # Scaffold logic
├── store_test.go
├── limiter_test.go
└── go.mod

CLI

pubengine new
pubengine new github.com/yourname/myblog

Creates a new project directory with everything needed to run a blog. The last segment of the module path becomes the directory name (myblog).

Template variables:

  • {{.ProjectName}} is the directory name (e.g., myblog)
  • {{.ModuleName}} is the full module path (e.g., github.com/yourname/myblog)
  • {{.SiteName}} is the title cased name (e.g., Myblog)
pubengine version
pubengine version

Scaffolded project commands

After pubengine new, the generated Makefile and package.json provide:

Make targets
make run          # Generate templates, build CSS + JS, start server
make templ        # Regenerate templ templates
make css          # Build Tailwind CSS
make css-prod     # Production CSS (minified)
make js           # Bundle and minify src/app.js
make test         # Run Go tests
make build-linux  # Cross compile for Linux
npm scripts
npm run css          # Build Tailwind CSS (minified)
npm run css:watch    # Watch mode for CSS
npm run js           # Bundle and minify src/app.js via esbuild
npm run js:watch     # Watch mode for JS
npm run build        # Build both CSS and JS

Environment variables

Variable Required Default Description
ADMIN_PASSWORD yes Admin login password
ADMIN_SESSION_SECRET yes Session encryption secret (32+ chars)
SITE_NAME no Blog Site name for nav, RSS, JSON-LD
SITE_URL no http://localhost:3000 Canonical URL for sitemap and OpenGraph
SITE_DESCRIPTION no "" Description for RSS and meta tags
SITE_AUTHOR no "" Author name for JSON-LD
COOKIE_SECURE no false Set true behind HTTPS
GOOGLE_CLIENT_ID no "" Google OAuth client ID
GOOGLE_CLIENT_SECRET no "" Google OAuth client secret
GOOGLE_ADMIN_EMAIL no "" Allowed Google email for admin login
DATABASE_PATH no data/blog.db Blog SQLite path
ANALYTICS_DATABASE_PATH no data/analytics.db Analytics SQLite path
ADDR no :3000 Server listen address

Dependencies

Package Version Purpose
echo/v4 v4.14.0 HTTP framework
templ v0.3.960 Type safe HTML templates
modernc.org/sqlite v1.44.2 Pure Go SQLite driver
gorilla/sessions v1.2.2 Cookie session management
echo-contrib v0.17.1 Echo session middleware

No JavaScript framework dependencies. HTMX and the analytics script are embedded in the binary.

Testing

# Run all tests
go test ./...

# Run with verbose output
go test -v ./...

# Run benchmarks
go test -bench=. ./...

Test coverage includes store operations, rate limiting, and markdown rendering.

Deployment

pubengine compiles to a single binary. Deploy it with your public/ directory and a data/ directory for SQLite:

# Build for Linux
GOOS=linux GOARCH=amd64 go build -o mysite .

# On the server
./mysite
# Needs: public/ directory, data/ directory (auto created), env vars set

The binary embeds HTMX, the analytics script, the analytics dashboard JS, and the admin CSS. User assets (CSS, JS, fonts, images) live in the public/ directory alongside the binary.

License

MIT

Documentation

Overview

Package pubengine is a blog publishing engine built with Go, Echo, and templ. It provides blog CRUD, admin dashboard, analytics, RSS, and sitemap out of the box.

Users provide their own templ templates via the ViewFuncs struct, and pubengine handles all the handler logic, middleware, and database operations.

Index

Constants

This section is empty.

Variables

View Source
var EmbeddedAssets embed.FS

EmbeddedAssets contains static assets shipped with the framework: htmx.min.js, analytics.js, dashboard.min.js

View Source
var ErrNotFound = sql.ErrNoRows

ErrNotFound is returned when a requested post does not exist.

Functions

func BlogPostingJsonLD

func BlogPostingJsonLD(post BlogPost, cfg SiteConfig) string

BlogPostingJsonLD returns a JSON-LD string for a BlogPosting schema.

func BuildURL

func BuildURL(base string, pathSegments ...string) string

BuildURL joins a base URL with path segments, ensuring a trailing slash.

func CsrfToken

func CsrfToken(c echo.Context) string

CsrfToken extracts the CSRF token from the Echo context.

func EnvOr

func EnvOr(key, fallback string) string

EnvOr returns the value of the environment variable key, or fallback if empty. This is a convenience function for use in scaffolded main.go files.

func FilterEmpty

func FilterEmpty(vals []string) []string

FilterEmpty removes empty/whitespace-only strings from a slice.

func IsAdmin

func IsAdmin(c echo.Context) bool

IsAdmin checks if the current session is authenticated.

func JoinTags

func JoinTags(tags []string) string

JoinTags joins tags with ", ".

func MustEnv

func MustEnv(key string) string

MustEnv returns the value of the environment variable key, or fatally exits if empty.

func ParseTags

func ParseTags(tagString string) []string

ParseTags splits a comma-delimited tag string (e.g. ",go,web,") into a slice.

func PathEscape

func PathEscape(s string) string

PathEscape escapes a string for use in a URL path.

func Render

func Render(c echo.Context, cmp templ.Component) error

Render writes a templ component as an HTTP 200 HTML response.

func RenderStatus

func RenderStatus(c echo.Context, code int, cmp templ.Component) error

RenderStatus writes a templ component with a specific HTTP status code.

func Slugify

func Slugify(s string) string

Slugify converts a title to a URL-safe slug.

func ValidateSlug

func ValidateSlug(slug string) string

ValidateSlug checks that a slug is non-empty, not reserved, and within length limits. Returns an error message string, or "" if valid.

func WebsiteJsonLD

func WebsiteJsonLD(cfg SiteConfig) string

WebsiteJsonLD returns a JSON-LD string for a WebSite schema using SiteConfig.

Types

type App

type App struct {
	Config SiteConfig
	Echo   *echo.Echo
	Store  *Store
	Cache  *PostCache
	Views  ViewFuncs
	// contains filtered or unexported fields
}

App is the central pubengine application. It wires together the store, cache, handlers, middleware, and user-provided templates.

func New

func New(cfg SiteConfig, views ViewFuncs, opts ...Option) *App

New creates a new pubengine App with the given configuration and view functions.

func (*App) Close

func (a *App) Close() error

Close cleans up resources. Call this when the app is shutting down.

func (*App) Start

func (a *App) Start() error

Start initializes the database, cache, middleware, routes, and starts the server.

type BlogPost

type BlogPost struct {
	Title     string
	Date      string
	Tags      []string
	Summary   string
	Link      string
	Slug      string
	Content   string
	Published bool
}

BlogPost is the core content type stored in SQLite and rendered by templates.

func FilterRelatedPosts

func FilterRelatedPosts(current BlogPost, posts []BlogPost) []BlogPost

FilterRelatedPosts finds posts that share at least one tag with current.

type Image

type Image struct {
	Filename     string // e.g. "my-photo.jpg"
	OriginalName string
	Width        int
	Height       int
	Size         int    // bytes
	UploadedAt   string // RFC3339
}

Image represents an uploaded image stored in the uploads directory.

type LoginLimiter

type LoginLimiter struct {
	// contains filtered or unexported fields
}

LoginLimiter rate-limits login attempts per IP address.

func NewLoginLimiter

func NewLoginLimiter(max int, window time.Duration) *LoginLimiter

NewLoginLimiter creates a LoginLimiter that allows max attempts per window.

func (*LoginLimiter) Allow

func (l *LoginLimiter) Allow(ip string) bool

Allow checks if the IP has not exceeded the rate limit and records the attempt. Kept for backwards compatibility; prefer Check + Record for login flows.

func (*LoginLimiter) Check

func (l *LoginLimiter) Check(ip string) bool

Check returns true if the IP has not exceeded the rate limit. It does not record an attempt — call Record separately on failure.

func (*LoginLimiter) Record

func (l *LoginLimiter) Record(ip string)

Record registers a failed login attempt for the given IP.

type Option

type Option func(*App)

Option configures additional App behavior.

func WithCustomRoutes

func WithCustomRoutes(fn func(*App)) Option

WithCustomRoutes registers additional routes on the Echo instance. The callback receives the Echo instance before the server starts.

func WithStaticDir

func WithStaticDir(dir string) Option

WithStaticDir sets the directory for user-owned static assets (default "public").

type PageMeta

type PageMeta struct {
	Title       string
	Description string
	URL         string // canonical + og:url
	OGType      string // "website" or "article"
}

PageMeta carries per-page OpenGraph and SEO metadata into the <head> template.

type PostCache

type PostCache struct {
	// contains filtered or unexported fields
}

PostCache is an in-memory cache of published blog posts and tags with TTL.

func NewPostCache

func NewPostCache(s *Store, ttl time.Duration) *PostCache

NewPostCache creates a PostCache backed by the given Store.

func (*PostCache) GetPost

func (c *PostCache) GetPost(slug string) (BlogPost, error)

GetPost returns a single published post by slug from the cache.

func (*PostCache) Invalidate

func (c *PostCache) Invalidate()

Invalidate clears the cache so the next read triggers a fresh load.

func (*PostCache) ListPosts

func (c *PostCache) ListPosts(tag string) ([]BlogPost, error)

ListPosts returns published posts, optionally filtered by tag.

func (*PostCache) ListTags

func (c *PostCache) ListTags() ([]string, error)

ListTags returns all unique tags from published posts.

type SiteConfig

type SiteConfig struct {
	Name        string // Site name (default "Blog")
	URL         string // Canonical URL (default "http://localhost:3000")
	Description string // Site description for RSS and meta tags
	Author      string // Author name for JSON-LD

	Addr         string // Listen address (default ":3000")
	DatabasePath string // SQLite path (default "data/blog.db")

	AnalyticsEnabled      bool   // Enable analytics (default false; scaffold sets true)
	AnalyticsDatabasePath string // Analytics SQLite path (default "data/analytics.db")

	AdminPassword string // Required: admin login password
	SessionSecret string // Required: session encryption secret
	CookieSecure  bool   // Set true for HTTPS

	GoogleClientID     string // Google OAuth client ID (optional)
	GoogleClientSecret string // Google OAuth client secret (optional)
	GoogleAdminEmail   string // Allowed Google email for admin login (optional)

	PostCacheTTL time.Duration // Post cache TTL (default 5min)
}

SiteConfig holds all configuration for a pubengine site.

func (*SiteConfig) GoogleAuthEnabled

func (c *SiteConfig) GoogleAuthEnabled() bool

GoogleAuthEnabled returns true when all three Google OAuth fields are configured.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store wraps a SQLite database and provides CRUD operations for blog posts.

func NewStore

func NewStore(path string) (*Store, error)

NewStore opens (or creates) the SQLite database at path, ensures the data directory exists, and runs schema migrations.

func (*Store) Close

func (s *Store) Close() error

Close closes the underlying database connection.

func (*Store) DeleteImage

func (s *Store) DeleteImage(filename string) error

DeleteImage removes image metadata from the database.

func (*Store) DeletePost

func (s *Store) DeletePost(slug string) error

DeletePost removes a post by slug.

func (*Store) GetPost

func (s *Store) GetPost(slug string) (BlogPost, error)

GetPost returns a single published post by slug.

func (*Store) GetPostAny

func (s *Store) GetPostAny(slug string) (BlogPost, error)

GetPostAny returns a post by slug regardless of published status (for admin).

func (*Store) ListAllPosts

func (s *Store) ListAllPosts() ([]BlogPost, error)

ListAllPosts returns every post (published and drafts) ordered by date descending.

func (*Store) ListImages

func (s *Store) ListImages() ([]Image, error)

ListImages returns all images ordered by upload time descending.

func (*Store) ListPosts

func (s *Store) ListPosts(tag string) ([]BlogPost, error)

ListPosts returns all published posts ordered by date descending. If tag is non-empty, results are filtered to posts containing that tag.

func (*Store) ListTags

func (s *Store) ListTags() ([]string, error)

ListTags returns a sorted, deduplicated slice of all tags from published posts.

func (*Store) SaveImage

func (s *Store) SaveImage(img Image) error

SaveImage inserts image metadata into the database.

func (*Store) SavePost

func (s *Store) SavePost(p BlogPost) error

SavePost upserts a blog post. Tags are normalized to lowercase.

type ViewFuncs

type ViewFuncs struct {
	Home             func(posts []BlogPost, activeTag string, tags []string, siteURL string) templ.Component
	HomePartial      func(posts []BlogPost, activeTag string, tags []string, siteURL string) templ.Component
	BlogSection      func(posts []BlogPost, activeTag string, tags []string) templ.Component
	Post             func(post BlogPost, posts []BlogPost, siteURL string) templ.Component
	PostPartial      func(post BlogPost, posts []BlogPost, siteURL string) templ.Component
	AdminLogin       func(errorMsg string, csrfToken string, googleLoginURL string) templ.Component
	AdminDashboard   func(posts []BlogPost, message string, csrfToken string) templ.Component
	AdminFormPartial func(post BlogPost, csrfToken string) templ.Component
	AdminImages      func(images []Image, csrfToken string) templ.Component
	NotFound         func() templ.Component
	ServerError      func() templ.Component
}

ViewFuncs holds user-provided templ components that the framework calls when rendering pages. This is the inversion-of-control mechanism that lets users own and customize all templates.

Directories

Path Synopsis
Package analytics provides privacy-first website analytics.
Package analytics provides privacy-first website analytics.
templates
templ: version: v0.3.977
templ: version: v0.3.977
cmd
pubengine command
Package markdown provides a simple Markdown-to-HTML renderer as a templ component.
Package markdown provides a simple Markdown-to-HTML renderer as a templ component.
Package scaffold provides embedded template files for the pubengine CLI project scaffolding tool.
Package scaffold provides embedded template files for the pubengine CLI project scaffolding tool.

Jump to

Keyboard shortcuts

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