shopadmin

package module
v0.1.0 Latest Latest
Warning

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

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

README

shopadmin

Standalone shop admin interface module for Go. Provides a ready-to-use admin panel for managing products, categories, discounts, and orders, built on top of github.com/dracory/shopstore.

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

Features

  • Product management — create, update, delete, list with AJAX
  • Category management — create, update, delete, list with AJAX
  • Discount management — create, delete, list with AJAX
  • Order management — list orders, view order details
  • Media management — upload, reorder, delete product images
  • Metadata & tags — per-product metadata and tag editing
  • Customer resolution — pluggable via CustomerResolverInterface (no dependency on any specific user/auth package)
  • Custom layouts — bring your own layout via FuncLayout
  • Bootstrap + Vue CDN — default UI works out of the box

Installation

go get github.com/dracory/shopadmin

Quick Start

package main

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

    "github.com/dracory/shopadmin"
    "github.com/dracory/shopstore"
)

func main() {
    store, err := shopstore.NewStore(shopstore.NewStoreOptions{
        DB:                 yourDB,
        ProductTableName:   "shop_product",
        CategoryTableName:  "shop_category",
        // ... other table names
        AutomigrateEnabled: true,
    })
    if err != nil {
        log.Fatal(err)
    }

    admin, err := shopadmin.New(shopadmin.AdminOptions{
        Store:       store,
        Logger:      slog.New(slog.NewTextHandler(os.Stderr, nil)),
        AdminHomeURL: "/admin",
        ShopAdminURL: "/admin/shop",
    })
    if err != nil {
        log.Fatal(err)
    }

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

Integration with a Router

Use Routes() to get rtr.RouteInterface slices for integration with the host project's router:

routes, err := shopadmin.Routes(app, shopadmin.AdminOptions{
    ShopAdminURL: links.Admin().Shop(),
    AdminHomeURL: links.Admin().Home(),
})

app must implement shopadmin.RegistryInterface:

type RegistryInterface interface {
    GetShopStore() shopstore.StoreInterface
    GetCacheStore() cachestore.StoreInterface
    GetLogger() *slog.Logger
}

Customer Resolution

Shopadmin does not depend on userstore or any specific auth package. Instead, order controllers resolve customer names/emails via an optional CustomerResolverInterface:

type CustomerResolverInterface interface {
    FindByID(ctx context.Context, customerID string) (name, email string)
    SearchIDs(ctx context.Context, name, email string) ([]string, error)
}

Provide an implementation at construction time:

admin, _ := shopadmin.New(shopadmin.AdminOptions{
    Store:  store,
    Logger: logger,
    CustomerResolver: &myCustomerResolver{userStore: app.GetUserStore()},
})

If CustomerResolver is nil, customer fields stay empty and customer filtering is disabled — no panic, no error.

See docs/proposal.md for the full design rationale.

Custom Layout

By default, shopadmin 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, _ := shopadmin.New(shopadmin.AdminOptions{
    Store:  store,
    Logger: logger,
    FuncLayout: func(title, body string, opts struct {
        Styles     []string
        StyleURLs  []string
        Scripts    []string
        ScriptURLs []string
    }) string {
        return myLayout(title, body, opts)
    },
})

The anonymous struct matches cmsstore/admin exactly, so you can reuse your existing cmsstore layout function.

Authentication

Provide an AuthUserID function to gate access. If it returns "", the request is redirected to AdminHomeURL:

admin, _ := shopadmin.New(shopadmin.AdminOptions{
    Store:      store,
    Logger:     logger,
    AuthUserID: func(r *http.Request) string {
        // return authenticated user ID, or ""
    },
})

Project Structure

shopadmin/
├── shopadmin.go              # AdminOptions, New(), Handle()
├── routes.go                 # Routes() for router integration
├── registry.go               # RegistryInterface
├── types.go                  # CustomerResolverInterface (re-export)
├── errors.go                 # Sentinel errors
├── context.go                # Request context helpers
├── controllers.go            # buildControllerRoutes()
├── shared/                   # Shared UI infrastructure
│   ├── ui_config.go          # UiConfig, CustomerResolverInterface
│   ├── ui_base.go            # UiBase (embeds into controllers)
│   ├── ui_interface.go       # UiInterface
│   ├── layout.go             # Default layout
│   ├── flash.go              # Flash messages
│   ├── header.go             # Admin header
│   └── links.go              # URL builders
├── home/                     # Dashboard controller
├── product_manager/          # Product list controller
├── product_update/           # Product edit controller
├── product_delete/           # Product delete controller
├── category_manager/         # Category list controller
├── category_create/          # Category create controller
├── category_update/          # Category update controller
├── discount_manager/         # Discount list controller
├── order_manager/            # Order list controller
├── order_details/            # Order details controller
├── testutils/                # Test utilities
└── docs/
    └── proposal.md           # CustomerResolverInterface design doc

Testing

go test ./...

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

Dependencies

Not dependent on userstore — customer resolution is via CustomerResolverInterface.

License

See the project repository for license information.

Documentation

Overview

Package shopadmin provides a standalone shop 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/cmsstore/admin.

Index

Constants

This section is empty.

Variables

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

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

	// ErrRegistryRequired is returned when Registry is not provided
	// (kept for backward compatibility with Routes())
	ErrRegistryRequired = errors.New("registry is required")
)

Functions

func Routes

func Routes(registry RegistryInterface, opts ...AdminOptions) ([]rtr.RouteInterface, error)

Routes returns the routes for the shop admin, for integration with the host project's router. The signature preserves the original Routes(registry, opts...) form — registry is RegistryInterface (structurally compatible with project/internal/app.AppInterface), so existing call sites like shopadmin.Routes(app, opts) work unchanged.

Types

type AdminInterface

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

AdminInterface defines the interface for the shop admin

func New

func New(opts AdminOptions) (AdminInterface, error)

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

type AdminOptions

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

	// Logger is required (matches cmsstore requirement)
	Logger *slog.Logger

	// CustomerResolver resolves customer data for order views.
	// Optional — nil means customer fields stay empty and customer
	// filtering is disabled. Called by order_details and order_manager.
	CustomerResolver CustomerResolverInterface

	// 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 cmsstore/admin exactly, so consumers
	// can reuse their cmsstore layout function for shopadmin.
	FuncLayout func(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

	// ShopAdminURL is the base URL for shop admin (default: "/admin/shop")
	ShopAdminURL 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 shop admin.

Store, CacheStore, and Logger replace the in-repo version's Registry field (which was app.AppInterface). This matches the cmsstore/admin convention where stores are passed directly.

Customer resolution is via CustomerResolverInterface rather than a userstore dependency, keeping shopadmin decoupled from any specific auth/user-management package.

type CustomerResolverInterface

type CustomerResolverInterface = shared.CustomerResolverInterface

CustomerResolverInterface resolves customer data for order views. The host project provides an implementation — shopadmin does not care where the data comes from (userstore, CRM, external API, etc.).

This is a re-export of shared.CustomerResolverInterface so consumers of the root shopadmin package can use shopadmin.CustomerResolverInterface without importing the shared subpackage.

type RegistryInterface

type RegistryInterface interface {
	GetShopStore() shopstore.StoreInterface
	GetLogger() *slog.Logger
}

RegistryInterface provides access to the stores and services that shopadmin needs. It is structurally compatible with project/internal/app.AppInterface — any type implementing AppInterface already satisfies this narrower interface.

This interface exists so that Routes() can preserve its original signature Routes(registry, opts...) without importing project/internal/app.

GetUserStore() is intentionally absent — customer resolution is handled via FindCustomer / SearchCustomerIDs function fields in AdminOptions, keeping shopadmin decoupled from userstore.

Directories

Path Synopsis
Example shopadmin server.
Example shopadmin server.

Jump to

Keyboard shortcuts

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