229 lines
5.7 KiB
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)
|
|
}
|
|
}
|
|
}
|