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, "[Wasp] ", 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(cron.WithSeconds()) 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) } } }