package dev import ( "bytes" "fmt" "io" "log" "path" "phc/website/services/config" "phc/website/services/server/routes" "phc/website/sl" "github.com/alecthomas/repr" "github.com/gofiber/fiber/v2" ) // 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 { log.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"` Page 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 } repr.Print(data) 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.Page), 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) { log.Printf(`registering vite route %q for %q`, frontendFile, mountPoint) dev, err := sl.Use(l, slot) if err != nil { log.Fatal(err) } dev.staticRoutes[mountPoint] = frontendFile log.Print(dev) } func RegisterDynamicRoute(l *sl.ServiceLocator, mountPoint, frontendFile string, handler Handler) { log.Printf(`registering vite route %q for %q`, frontendFile, mountPoint) dev, err := sl.Use(l, slot) if err != nil { log.Fatal(err) } dev.dynamicRoutes[mountPoint] = frontendFile dev.dynamicRoutesHandlers[mountPoint] = handler } func GetArtifactPath(frontendFile string) string { return path.Join("./out/frontend/", frontendFile) }