package dev import ( "bytes" "fmt" "io" "log" "os" "path" "git.phc.dm.unipi.it/phc/website/services/config" "git.phc.dm.unipi.it/phc/website/services/server/routes" "git.phc.dm.unipi.it/phc/website/sl" "github.com/alecthomas/repr" "github.com/gofiber/fiber/v2" ) // Logger is the debug logger, in the future this will be disabled and discard by default. var Logger *log.Logger = log.New(os.Stderr, "[services/server/dev] ", log.Lmsgprefix) // slot represents a private "write only" service var slot = sl.NewSlot[*devService]() // InjectInto a [*sl.ServiceLocator] an instance of the dev service func InjectInto(l *sl.ServiceLocator) { sl.InjectLazy(l, slot, Configure) } func UseRoutesMetadata(l *sl.ServiceLocator) map[string]any { dev, err := sl.Use(l, slot) if err != nil { Logger.Fatal(err) } return map[string]any{ "static": dev.staticRoutes, "dynamic": dev.dynamicRoutes, } } type Request interface { Page() []byte Param(key string) string Query(key string) string } type ResponseWriter interface { io.Writer } // devServerRequest is used when handling request from the dev server where params and queries are parsed by express type devServerRequest struct { page []byte params map[string]string query map[string]string } func (r devServerRequest) Page() []byte { return r.page } func (r devServerRequest) Param(key string) string { return r.params[key] } func (r devServerRequest) Query(key string) string { return r.query[key] } // Handler is a custom routes handler type Handler func(ResponseWriter, Request) error type devService struct { staticRoutes map[string]string dynamicRoutes map[string]string dynamicRoutesHandlers map[string]Handler } func Configure(l *sl.ServiceLocator) (*devService, error) { d := &devService{ map[string]string{}, map[string]string{}, map[string]Handler{}, } r, err := sl.Use(l, routes.Root) if err != nil { return nil, err } config, _ := sl.Use(l, config.Slot) if config.Mode != "development" { return d, nil } r.Get("/api/development/routes", func(c *fiber.Ctx) error { return c.JSON(map[string]any{ "static": d.staticRoutes, "dynamic": d.dynamicRoutes, }) }) r.Post("/api/development/render", func(c *fiber.Ctx) error { var data struct { Route string `json:"route"` HtmlPage string `json:"page"` Request struct { ParamsMap map[string]string `json:"params"` QueryMap map[string]string `json:"query"` } `json:"request"` } if err := c.BodyParser(&data); err != nil { return err } Logger.Printf(`server rendering route "%s"`, data.Route) Logger.Printf(`- params: %s`, repr.String(data.Request.ParamsMap)) Logger.Printf(`- query: %s`, repr.String(data.Request.QueryMap)) handler, ok := d.dynamicRoutesHandlers[data.Route] if !ok { return fmt.Errorf(`no handler for "%s"`, data.Route) } var buf bytes.Buffer if err := handler(&buf, devServerRequest{ []byte(data.HtmlPage), data.Request.ParamsMap, data.Request.QueryMap, }); err != nil { return err } return c.JSON(buf.String()) }) return d, nil } // RegisterRoute will register the provided "mountPoint" to the "frontendHtml" page func RegisterRoute(l *sl.ServiceLocator, mountPoint, frontendFile string) { dev, err := sl.Use(l, slot) if err != nil { Logger.Fatal(err) } dev.staticRoutes[mountPoint] = frontendFile Logger.Printf(`registered vite route "%v" for "%v"`, frontendFile, mountPoint) } func RegisterDynamicRoute(l *sl.ServiceLocator, mountPoint, frontendFile string, handler Handler) { dev, err := sl.Use(l, slot) if err != nil { Logger.Fatal(err) } dev.dynamicRoutes[mountPoint] = frontendFile dev.dynamicRoutesHandlers[mountPoint] = handler Logger.Printf(`registered vite route "%v" for "%v"`, frontendFile, mountPoint) } func GetArtifactPath(frontendFile string) string { return path.Join("./out/frontend/", frontendFile) }