Documentation ¶
Overview ¶
Package gosaas contains helper functions, middlewares, user management and billing functionalities commonly used in typical Software as a Service web application.
The primary goal of this library is to handle repetitive components letting you focus on the core part of your project.
You use the NewServer function to get a working server MUX. You need to pass the top level routes to the NewServer function to get the initial routing working.
For instance if your web application handles the following routes:
/task /task/mine /task/done /ping /ping/stat
You only pass the "task" and "ping" routes to the server. Anything after the top-level will be handled by your code. You will be interested in ShiftPath, Respond, ParseBody and ServePage functions to get started.
The most important aspect of a route is the Handler field which corresponds to the code to execute. The Handler is a standard http.Handler meaning that your code will need to implement the ServeHTTP function.
The remaining fields for a route control if specific middlewares are part of the request life-cycle or not. For instance, the Logger flag will output request information to stdout when enabled.
Index ¶
- Constants
- func Authenticator(next http.Handler) http.Handler
- func Cors(next http.Handler) http.Handler
- func ExtractLimitAndOffset(r *http.Request) (limit int, offset int)
- func Language(next http.Handler) http.Handler
- func Logger(next http.Handler) http.Handler
- func ParseBody(body io.ReadCloser, result interface{}) error
- func RateLimiter(next http.Handler) http.Handler
- func Respond(w http.ResponseWriter, r *http.Request, status int, data interface{}) error
- func SendWebhook(wh data.WebhookServices, event string, data interface{})
- func ServePage(w http.ResponseWriter, r *http.Request, name string, data interface{})
- func SetStripeKey(key string)
- func ShiftPath(p string) (head, tail string)
- func Throttler(next http.Handler) http.Handler
- func Translate(lng, key string) template.HTML
- func Translatef(lng, key string, a ...interface{}) string
- type Auth
- type Billing
- type BillingCardData
- type BillingNewCustomer
- type BillingOverview
- type DeleteSubscription
- type Notification
- type Route
- type Server
- type StripeWebhook
- type User
- type ViewData
- type Webhook
- type WebhookData
- type WebhookDataObject
- type WebhookDataObjectData
- Bugs
Examples ¶
Constants ¶
const ( // ContextOriginalPath holds the original requested URL. ContextOriginalPath key = iota // ContextRequestStart holds the request start time. ContextRequestStart // ContextDatabase holds a reference to a data.DB database connection and services. ContextDatabase // ContextAuth holds the authenticated user account id and user id. ContextAuth // ContextMinimumRole holds the minimum role to access this resource. ContextMinimumRole // ContextRequestID unique ID for the request. ContextRequestID // ContextRequestDump holds the request data dump. ContextRequestDump // ContextLanguage holds the request language. ContextLanguage // ContextContentIsJSON indicates if the request Content-Type is application/json ContextContentIsJSON )
Variables ¶
This section is empty.
Functions ¶
func Authenticator ¶
Authenticator middleware used to authenticate requests.
There are 4 ways to authenticate a request: 1. Via an HTTP header named X-API-KEY. 2. Via a querystring parameter named "key=token". 3. Via a cookie named X-API-KEY. 4. Via basic authentication.
For routes with MinimumRole set as model.RolePublic there's no authentication performed.
func ExtractLimitAndOffset ¶
BUG(dom): This needs more thinking...
func Language ¶
Language is a middleware handling the language cookie named "lng".Language
This is used in HTML templates and Go code when using the Translate function. You need to create a language files inside a directory named languagepack (i.e. en.json, fr.json).
func Logger ¶
Logger is a middleware that log requests information to stdout.
If the request failed with a status code >= 300, a dump of the request will be saved into the cache store. You can investigate and replay the request in a development environment using this tool https://github.com/dstpierre/httpreplay.
func ParseBody ¶
func ParseBody(body io.ReadCloser, result interface{}) error
ParseBody parses the request JSON body into a struct.ParseBody
Example usage:
func handler(w http.ResponseWriter, r *http.Request) { var task Task if err := gosaas.ParseBody(r.Body, &task); err != nil { gosaas.Respond(w, r, http.StatusBadRequest, err) return } }
func RateLimiter ¶
RateLimiter is a middleware used to prevent too many call in short time span. If the maximum allowed requests per-user is reached it will return a StatusTooManyRequests error.
For clarity if maximum is reached a "Retry-After" HTTP header with the time in second the user will need to wait before sending another request.
func Respond ¶
Respond return an strruct with specific status as JSON.
If data is an error it will be wrapped in a generic JSON object:
{ "status": 401, "error": "the result of data.Error()" }
Example usage:
func handler(w http.ResponseWriter, r *http.Request) { task := Task{ID: 123, Name: "My Task", Done: false} gosaas.Respond(w, r, http.StatusOK, task) }
func SendWebhook ¶
func SendWebhook(wh data.WebhookServices, event string, data interface{})
SendWebhook posts data to all subscribers of an event.
func ServePage ¶
func ServePage(w http.ResponseWriter, r *http.Request, name string, data interface{})
ServePage will render and respond with an HTML template.ServePage
HTML templates should be saved into a directory named templates.ServePage ¶
Example usage:
func handler(w http.ResponseWriter, r *http.Request) { data := HomePage{Title: "Hello world!"} gosaas.ServePage(w, r, "index.html", data) }
func ShiftPath ¶
ShiftPath splits the request URL head and tail.
This is useful to perform routing inside the ServeHTTP function.ShiftPath ¶
Example usage:
package yourapp import ( "net/http" "github.com/dstpierre/gosaas" ) func main() { routes := make(map[string]*gosaas.Route) routes["speak"] = &gosaas.Route{Handler: speak{}} mux := gosaas.NewServer(routes) http.ListenAndServe(":8080", mux) } type speak struct{} func (s speak) ServeHTTP(w http.ResponseWriter, r *http.Request) { var head string head, r.URL.Path = gosaas.ShiftPath(r.URL.Path) if head == "loud" { s.scream(w, r) } else { s.whisper(w, r) } }
func Throttler ¶
Throttler is a middleware used to throttle and apply rate limit to requests.
This is currently set to 9999 calls limit per day. If the limit is reached the middleware returns an error with the code StatusTooManyRequests.
func Translate ¶
Translate finds a key in a language pack file (saved in directory named languagepack) and return the value as template.HTML so it's safe to use HTML inside the language pack file.Translate
The language pack file are simple JSON file named lng.json like en.json:
{ "lang": "en", "keys": [ {"key": "landing-title", "value": "Welcome to my site"} ] }
func Translatef ¶
Translatef finds a translation key and substitute the formatting parameters.
Types ¶
type Billing ¶
Billing handles everything related to the billing requests
func (Billing) Convert ¶
func (b Billing) Convert(bc BillingNewCustomer) error
Convert upgrades an existing trial account to a paid account.
func (Billing) Start ¶
func (b Billing) Start(bc BillingNewCustomer) error
type BillingCardData ¶
type BillingCardData struct { ID string `json:"id"` Name string `json:"name"` Number string `json:"number"` Month string `json:"month"` Year string `json:"year"` CVC string `json:"cvc"` Brand string `json:"brand"` Expiration string `json:"expiration"` }
BillingCardData represents a Stripe credit card
type BillingNewCustomer ¶
type BillingNewCustomer struct { AccountID int64 Email string Plan string StripeToken string Coupon string IsPerSeat bool IsYearly bool TrialDays int Quantity int }
BillingNewCustomer represents data sent to api for creating a new customer
type BillingOverview ¶
type BillingOverview struct { Account *model.Account `json:"account"` StripeID string `json:"stripeId"` Plan string `json:"plan"` IsYearly bool `json:"isYearly"` IsNew bool `json:"isNew"` Cards []BillingCardData `json:"cards"` CostForNewUser int `json:"costForNewUser"` CurrentPlan *data.BillingPlan `json:"currentPlan"` Seats int `json:"seats"` Logins []model.User `json:"logins"` NextInvoice *stripe.Invoice `json:"nextInvoice"` }
BillingOverview represents if an account is a paid customer or not
type DeleteSubscription ¶
type Notification ¶
type Notification struct { Title template.HTML Message template.HTML IsSuccess bool IsError bool IsWarning bool }
Notification can be used to display alert to the user in an HTML template.
type Route ¶
type Route struct { // middleware WithDB bool Logger bool EnforceRateLimit bool AllowCrossOrigin bool // authorization MinimumRole model.Roles Handler http.Handler }
Route represents a web handler with optional middlewares.
type Server ¶
type Server struct { DB *data.DB Logger func(http.Handler) http.Handler Authenticator func(http.Handler) http.Handler Throttler func(http.Handler) http.Handler RateLimiter func(http.Handler) http.Handler Cors func(http.Handler) http.Handler StaticDirectory string Routes map[string]*Route }
Server is the starting point of the backend.
Responsible for routing requests to handlers.
func NewServer ¶
NewServer returns a production server with all available middlewares. Only the top level routes needs to be passed as parameter.
There's three built-in implementations:
1. users: for user management (signup, signin, authentication, get detail, etc).
2. billing: for a fully functional billing process (converting from free to paid, changing plan, get invoices, etc).
3. webhooks: for allowing users to subscribe to events (you may trigger webhook via gosaas.SendWebhook).
To override default inplementation you simply have to supply your own like so:
routes := make(map[string]*gosaas.Route) routes["billing"] = &gosaas.Route{Handler: billing.Route}
This would use your own billing implementation instead of the one supplied by gosaas.
Example ¶
routes := make(map[string]*Route) routes["task"] = &Route{ Logger: true, // enable logging MinimumRole: model.RoleFree, // make sure only free user and up can access this route Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // normally you would use a custom type that implement ServerHTTP // and handle all sub-level route for this top-level route. Respond(w, r, http.StatusOK, "list of tasks....") }), } mux := NewServer(routes) if err := http.ListenAndServe(":8080", mux); err != nil { log.Fatal(err) }
Output:
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP is where the top level routes get matched with the map[string]*gosaas.Route received from the call to NewServer. Middleware are applied based on the found route properties.
If no route can be found an error is returned.
Static files are served from the "/public/" directory by default. To change this you may set the StaticDirectory after creating the server like this:
mux := gosaas.NewServer(routes) mux.StaticDirectory = "/files/"
type StripeWebhook ¶
StripeWebhook is used to grab data sent by Stripe for a webhook
type ViewData ¶
type ViewData struct { Language string Role model.Roles Alert *Notification Data interface{} }
ViewData is the base data needed for all pages to render.
It will automatically get the user's language, role and if there's an alert to display. You can view this a a wrapper around what you would have sent to the page being redered.
func CreateViewData ¶
func CreateViewData(ctx context.Context, alert *Notification, data interface{}) ViewData
CreateViewData wraps the data into a ViewData type where the language, role and notification will be automatically added along side the data.
type Webhook ¶
type Webhook struct{}
Webhook handles everything related to the /webhooks requests
POST /webhooks -> subscribe to events GET /webhooks -> get the list of subscriptions for current user POST /webhooks/unsub -> remove a subscription
type WebhookData ¶
type WebhookData struct { ID string `json:"id"` Type string `json:"type"` Data WebhookDataObject `json:"data"` }
WebhookData used when stripe webhook is call
type WebhookDataObject ¶
type WebhookDataObject struct {
Object WebhookDataObjectData `json:"object"`
}
WebhookDataObject is the container for the object received
Notes ¶
Bugs ¶
This needs more thinking...