useradmin

package module
v0.1.0 Latest Latest
Warning

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

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

README

useradmin

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/userstore. Provides a ready-to-use admin panel for managing users: list with AJAX, create, update, delete, and impersonate.

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

Features

  • User management — list with AJAX pagination/sorting/filtering, create, update, delete
  • User impersonation — sign in as another user for support/debugging
  • Geo integration — country and timezone pickers via geostore
  • Vault tokenization — pluggable vault tokenizer for encrypted user fields (first name, last name, email, phone, business name)
  • Blind index search — pluggable blind index stores for filtered search on tokenized fields
  • Custom layouts — bring your own layout via FuncLayout
  • Bootstrap + Vue CDN — default UI works out of the box

Installation

go get github.com/dracory/useradmin

Quick Start

package main

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

    "github.com/dracory/useradmin"

    "github.com/dracory/geostore"
    "github.com/dracory/sessionstore"
    "github.com/dracory/userstore"
)

func main() {
    userStore, _ := userstore.NewStore(userstore.NewStoreOptions{
        DB:                 yourDB,
        UserTableName:      "user",
        AutomigrateEnabled: true,
    })

    geoStore, _ := geostore.NewStore(geostore.NewStoreOptions{
        DB:                 yourDB,
        CountryTableName:   "geo_country",
        TimezoneTableName:  "geo_timezone",
        AutomigrateEnabled: true,
        AutoseedEnabled:    true,
    })

    sessionStore, _ := sessionstore.NewStore(sessionstore.NewStoreOptions{
        DB:                 yourDB,
        SessionTableName:   "session",
        AutomigrateEnabled: true,
    })

    admin, err := useradmin.New(useradmin.AdminOptions{
        UserStore:    userStore,
        GeoStore:     geoStore,
        Logger:       slog.New(slog.NewTextHandler(os.Stderr, nil)),
        SessionStore: sessionStore,
        AdminHomeURL: "/admin",
        UserAdminURL: "/admin/users",
    })
    if err != nil {
        log.Fatal(err)
    }

    http.Handle("/admin/users", 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

useradmin.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/users", http.HandlerFunc(admin.Handle))

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

Optional Dependencies

The following AdminOptions fields are optional. When nil/empty, the corresponding features degrade gracefully:

  • BlindIndexFirstName / BlindIndexLastName / BlindIndexEmail — when nil, filtered search by that field is disabled (the filter returns no matches instead of erroring).
  • TaskStore + BlindIndexRebuildTaskAlias — when nil/empty, the blind index rebuild enqueue on email change is skipped.
  • VaultTokenizer — when nil, user fields are treated as plain text (no tokenization/untokenization).
  • AuthUser — when nil, the create/delete/impersonate controllers treat the request as unauthenticated.
  • FlashRedirect — when nil, plain http.Redirect is used instead of a flash-message redirect.
  • FuncLayout — when nil, a default bare-bones HTML page is used (Bootstrap + Vue CDN).

Custom Layout

By default, useradmin 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, _ := useradmin.New(useradmin.AdminOptions{
    UserStore:    userStore,
    GeoStore:     geoStore,
    Logger:       logger,
    SessionStore: sessionStore,
    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 ./...

Documentation

Overview

Package useradmin provides a standalone user 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/blogadmin.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUserStoreRequired is returned when UserStore is not provided
	ErrUserStoreRequired = errors.New("user store is required")

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

	// ErrSessionStoreRequired is returned when SessionStore is not
	// provided. The impersonate controller needs it to create a new
	// session for the impersonated user.
	ErrSessionStoreRequired = errors.New("session store is required for the impersonate controller")

	// ErrGeoStoreRequired is returned when GeoStore is not provided.
	// The user update controller needs it to list countries and
	// timezones.
	ErrGeoStoreRequired = errors.New("geo store is required for the user update controller")
)

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 user admin

func New

func New(opts AdminOptions) (AdminInterface, error)

New creates a new user admin instance. Returns ErrUserStoreRequired if UserStore is nil, ErrLoggerRequired if Logger is nil, ErrGeoStoreRequired if GeoStore is nil, and ErrSessionStoreRequired if SessionStore is nil.

This makes misconfiguration fail fast at construction instead of surfacing as runtime errors inside individual controllers.

type AdminOptions

type AdminOptions struct {
	// UserStore is required
	UserStore userstore.StoreInterface

	// GeoStore is required for the user update controller (country and
	// timezone lists).
	GeoStore geostore.StoreInterface

	// Logger is required
	Logger *slog.Logger

	// SessionStore is required for the impersonate controller.
	SessionStore sessionstore.StoreInterface

	// BlindIndexFirstName/LastName/Email enable filtered search by
	// the corresponding field. Optional.
	BlindIndexFirstName blindindexstore.StoreInterface
	BlindIndexLastName  blindindexstore.StoreInterface
	BlindIndexEmail     blindindexstore.StoreInterface

	// TaskStore is used to enqueue a blind index rebuild when a user's
	// email changes and vault tokenization is enabled. Optional.
	TaskStore taskstore.StoreInterface

	// BlindIndexRebuildTaskAlias is the task alias enqueued on email
	// change. If empty, the enqueue is skipped.
	BlindIndexRebuildTaskAlias string

	// VaultTokenizer abstracts vault tokenization. Optional — when
	// nil, user fields are treated as plain text.
	VaultTokenizer shared.VaultTokenizer

	// AuthUser returns the authenticated user from the request, or
	// nil if unauthenticated. Used by the create/delete/impersonate
	// controllers for authorization checks.
	AuthUser func(r *http.Request) userstore.UserInterface

	// AuthUserID returns the authenticated user ID from the request.
	// If it returns "", the user is treated as unauthenticated and
	// redirected to AdminHomeURL. If nil, the auth check is skipped
	// (the host project is expected to gate the route).
	AuthUserID func(r *http.Request) string

	// FlashRedirect redirects with a flash message. Optional — when
	// nil, plain http.Redirect is used.
	FlashRedirect shared.FlashRedirectFunc

	// SecureCookie controls whether the impersonation cookie is marked
	// Secure. Set to false for HTTP (development), true for HTTPS
	// (production). Defaults to true.
	SecureCookie bool

	// 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

	// UserAdminURL is the base URL for the user admin (default: "/admin/users")
	UserAdminURL string

	// UserHomeURL is the URL the impersonate controller redirects to
	// after a successful impersonation (default: "/"). This is where
	// the impersonated user lands.
	UserHomeURL string
}

AdminOptions contains all dependencies and configuration for the user admin.

UserStore, GeoStore, and Logger are required. SessionStore is required for the impersonate controller. Blind index stores, TaskStore, and VaultTokenizer are optional — when nil, the corresponding features degrade gracefully (filtered search disabled, email-change rebuild skipped, user fields treated as plain text).

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 an anonymous struct to match blogadmin/shopadmin exactly, so consumers can reuse their existing layout function for useradmin.

Directories

Path Synopsis
Example useradmin server.
Example useradmin server.

Jump to

Keyboard shortcuts

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