ponzu-cms / live

Ponzu CMS package to add support for live queries. Subscribe to / emit from various hooks, i.e. AfterAPICreate, BeforeAdminDelete, etc.
BSD 3-Clause "New" or "Revised" License
5 stars 0 forks source link
database golang hook ponzu-cms reactive

Live Queries

This package attempts to add a basic version of live queries to Ponzu. By interacting with the database hooks provided with the item.Hookable interface, the live package (import "github.com/ponzu-cms/live") enables users to publish and subscribe to the hookable events throughout the content package.

Usage:

package content

import (
    "fmt"
    "log"
    "net/http"

    "github.com/ponzu-cms/live"
    "github.com/ponzu-cms/ponzu/management/editor"
    "github.com/ponzu-cms/ponzu/system/item"
)

// typical generated ponzu content type
type Customer struct {
    item.Item

    Name  string `json:"name"`
    Email string `json:"email"`
}

// declare a package-level subscriptions container (could be in it's own file 
// within the content package)
var subs live.Subscriptions

func init() {
    item.Types["Customer"] = func() interface{} { return new(Customer) }

    // create the new subscriptions container to Subscribe / Emit
    subs = live.New()

    // create a subscriber to handle AdminUpdate events on a Customer
    go func() {
        adminUpdates := subs.Subscribe("Customer", live.AdminUpdate)
        for event := range adminUpdates {
            log.Println("<g1> UPDATE:", event.Content().(*Customer))
        }
    }()

    // NOTE: any number of subscriptions can be made to the same content type for
    // any event type.

    // create another subscriber for AdminDelete events on a Customer
    go func() {
        adminDeletes := subs.Subscribe("Customer", live.AdminDelete)
        for event := range adminDeletes {
            log.Println("<g2> DELETE:", event.Content().(*Customer))
        }
    }()
}

// emit content events from within the item.Hookable methods provided by Ponzu 
// to broadcast a change (hookable constants are defined in `live` for event types)
func (c *Customer) AfterAdminUpdate(res http.ResponseWriter, req *http.Request) error {
    err := subs.Emit(req.Context(), "Customer", c, live.AdminUpdate)
    if err != nil {
        if liveErr, ok := err.(live.QueryError); ok {
            // handle live error in specific manner
            fmt.Println(liveErr)
            return nil
        }

        return err
    }

    return nil
}

func (c *Customer) BeforeAdminDelete(res http.ResponseWriter, req *http.Request) error {
    err := subs.Emit(req.Context(), "Customer", c, live.AdminDelete)
    if err != nil {
        return err
    }

    return nil
}

// MarshalEditor writes a buffer of html to edit a Customer within the CMS
// and implements editor.Editable
func (c *Customer) MarshalEditor() ([]byte, error) {
    view, err := editor.Form(c,
        // Take note that the first argument to these Input-like functions
        // is the string version of each Customer field, and must follow
        // this pattern for auto-decoding and auto-encoding reasons:
        editor.Field{
            View: editor.Input("Name", c, map[string]string{
                "label":       "Name",
                "type":        "text",
                "placeholder": "Enter the Name here",
            }),
        },
        editor.Field{
            View: editor.Input("Email", c, map[string]string{
                "label":       "Email",
                "type":        "text",
                "placeholder": "Enter the Email here",
            }),
        },
    )

    if err != nil {
        return nil, fmt.Errorf("Failed to render Customer editor view: %s", err.Error())
    }

    return view, nil
}

// String defines how a Customer is printed. Update it using more descriptive
// fields from the Customer struct type
func (c *Customer) String() string {
    return fmt.Sprintf("Customer: %s", c.UUID)
}