go-dart/dart.go

229 lines
5.7 KiB
Go

package dart
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"slices"
"strings"
"github.com/gorilla/websocket"
"github.com/julienschmidt/httprouter"
"github.com/robfig/cron/v3"
)
type Dart struct {
router *httprouter.Router
errorHandler ErrorHandler
middlewares []Middleware
logger *log.Logger
database *Database
enableDatabase bool
websocketManager *WebsocketManager
enableWebsocketManager bool
cron *cron.Cron
enableCloudFunctions bool
}
func New() *Dart {
return &Dart{
router: httprouter.New(),
errorHandler: defaultErrorHandler,
middlewares: []Middleware{},
logger: log.New(os.Stdout, "[Dart] ", log.LstdFlags),
}
}
func (d *Dart) WithMySQLDatabase(cfg MySQLConfig) {
d.database = NewMySQLDatabase(cfg)
d.enableDatabase = true
}
func (d *Dart) WithPostgreSQLDatabase(cfg PostgreSQLConfig) {
d.database = NewPostgreSQLDatabase(cfg)
d.enableDatabase = true
}
func (d *Dart) WithSQLiteDatabase(cfg SQLiteConfig) {
d.database = NewSQLiteDatabase(cfg)
d.enableDatabase = true
}
func (d *Dart) DB() (*Database, error) {
if d.database == nil {
return nil, errors.New("database is not enabled")
}
return d.database, nil
}
func (d *Dart) WithWebsocketManager() {
d.websocketManager = &WebsocketManager{
conns: make(map[string]map[*websocket.Conn]bool),
}
d.enableWebsocketManager = true
}
func (d *Dart) WithCloudFunctions() {
d.cron = cron.New()
d.enableCloudFunctions = true
}
func (d *Dart) ScheduleCloudFunction(spec string, cf *CloudFunction) {
if !d.enableCloudFunctions {
d.logger.Println("Cloud function scheduling attempted but cloud functions are disabled")
return
}
ctx := &CloudContext{
database: d.database,
websocketManager: d.websocketManager,
state: make(map[string]any),
}
entryId, err := d.cron.AddFunc(spec, func() {
d.logger.Printf("Executing cloud function: %s", cf.Name)
if err := cf.Handler(ctx); err != nil {
d.logger.Printf("Error executing cloud function '%s': %v\n", cf.Name, err)
}
})
if err != nil {
d.logger.Printf("Error scheduling cloud function '%s': '%v\n", cf.Name, entryId)
return
}
cf.EntryID = entryId
}
func (d *Dart) Start(listenAddr string) error {
browsableUrl := listenAddr
if strings.HasPrefix(browsableUrl, ":") {
browsableUrl = fmt.Sprintf("http://localhost%s", browsableUrl)
}
if d.enableCloudFunctions {
d.cron.Start()
defer d.cron.Stop()
}
d.logger.Printf("Starting Dart app at %s\n", browsableUrl)
return http.ListenAndServe(listenAddr, d.router)
}
func (d *Dart) StartTLS(listenAddr, certFile, keyFile string) error {
browsableUrl := listenAddr
if strings.HasPrefix(browsableUrl, ":") {
browsableUrl = fmt.Sprintf("http://localhost%s", browsableUrl)
}
if d.enableCloudFunctions {
d.cron.Start()
defer d.cron.Stop()
}
d.logger.Printf("Starting Dart app at %s\n", browsableUrl)
return http.ListenAndServeTLS(listenAddr, certFile, keyFile, d.router)
}
func (d *Dart) SetErrorHandler(errorHandler ErrorHandler) {
d.errorHandler = errorHandler
}
func (d *Dart) Group(prefix string) *Group {
return &Group{
prefix: prefix,
dart: d,
}
}
func (d *Dart) Websocket(path string, middlewares ...Middleware) {
if !d.enableWebsocketManager {
d.logger.Println("Websocket registration attempted but websockets are disabled")
return
}
d.register(http.MethodGet, path, d.websocketManager.createHandler(), middlewares...)
}
func (d *Dart) Static(prefix, root string, middlewares ...Middleware) {
fileServer := http.FileServer(http.Dir(root))
wrappedHandler := d.createHandlerChain(func(ctx *Context) error {
http.StripPrefix(prefix, fileServer).ServeHTTP(ctx.response, ctx.request)
return nil
}, middlewares)
d.router.Handle(http.MethodGet, prefix+"/*filepath", wrappedHandler)
}
func (d *Dart) Use(middlewares ...Middleware) {
d.middlewares = append(d.middlewares, middlewares...)
}
func (d *Dart) GET(path string, h Handler, middlewares ...Middleware) {
d.register(http.MethodGet, path, h, middlewares...)
}
func (d *Dart) POST(path string, h Handler, middlewares ...Middleware) {
d.register(http.MethodPost, path, h, middlewares...)
}
func (d *Dart) PUT(path string, h Handler, middlewares ...Middleware) {
d.register(http.MethodPut, path, h, middlewares...)
}
func (d *Dart) DELETE(path string, h Handler, middlewares ...Middleware) {
d.register(http.MethodDelete, path, h, middlewares...)
}
func (d *Dart) PATCH(path string, h Handler, middlewares ...Middleware) {
d.register(http.MethodPatch, path, h, middlewares...)
}
func (d *Dart) HEAD(path string, h Handler, middlewares ...Middleware) {
d.register(http.MethodHead, path, h, middlewares...)
}
func (d *Dart) OPTIONS(path string, h Handler, middlewares ...Middleware) {
d.register(http.MethodOptions, path, h, middlewares...)
}
func (d *Dart) register(method string, path string, h Handler, middlewares ...Middleware) {
handlers := slices.Clone(middlewares)
finalHandler := d.createHandlerChain(h, handlers)
d.router.Handle(method, path, finalHandler)
}
func (d *Dart) createHandlerChain(h Handler, middlewares []Middleware) httprouter.Handle {
return func(rw http.ResponseWriter, r *http.Request, params httprouter.Params) {
ctx := &Context{
response: rw,
request: r,
params: params,
ctx: context.Background(),
database: d.database,
websocketManager: d.websocketManager,
}
finalHandler := h
for i := len(middlewares) - 1; i >= 0; i-- {
finalHandler = middlewares[i](finalHandler)
}
for i := len(d.middlewares) - 1; i >= 0; i-- {
finalHandler = d.middlewares[i](finalHandler)
}
if err := finalHandler(ctx); err != nil {
d.logger.Printf("Error in request %s %s: %v\n", r.Method, r.URL.Path, err)
d.errorHandler(err, ctx)
}
}
}