chore: refactored sl and added an hooks system

modified: cmd/dev-server/main.go, cmd/server/main.go

    Aggiornati per utilizzare il nuovo sistema degli hooks, ora nei "main" vanno
    solo chiamate "ProvideValue", "Provide" e "ProvideHook" ed una finale
    di "Invoke" o "Use". Mentre nei vari servizi vanno solo chiamate "Use"
    o "UseHook"

modified: libs/sl/sl.go

    Rinominati "InjectValue" e "InjectLazy" a "Provide" e "ProvideFunc", altro
    refactor di varie cose ed aggiunto il concetto degli hook per poter
    iniettare anche più di un servizio dentro un altro in un preciso ordine.

modified: server/config/config.go

    Lieve refactor ed aggiunta la variabile "NPM_COMMAND" configurabile per la
    fase di development.

modified: server/listautenti/listautenti.go

    Ora questo è un vero e proprio servizio "state-less", fornisce solo una
    funzione che monta tutte le route necessarie a questo servizio. Più avanti
    dipenderà da LDAP quindi forse sarà leggermente più complicato.

modified: server/listautenti/listautenti_test.go

    Aggiornato questo test, ora usa il nuovo sistema e funge di nuovo.

deleted: server/routes/routes.go

    Il sistema degli hook è stato introdotto anche per semplificare questa cosa
    che ora infatti non serve più.

modified: server/server.go

    Ora il server non chiama più "InjectValue" con i sotto-router ma espone solo
    un hook che passa i sotto router come parametri, in questo modo è più
    facile aggiungere e togliere servizi direttamente alla radice quando viene
    configurata tutta l'applicazione.
next-astro
parent be8c35229b
commit 01982942a8

@ -24,9 +24,15 @@ func init() {
func main() { func main() {
l := sl.New() l := sl.New()
cfg := sl.InjectValue(l, config.Slot, config.TestingDevelopmentConfig) //
// Setup the application
//
sl.InjectValue[database.Database](l, database.Slot, &database.Memory{ // Config
sl.ProvideFunc(l, config.Slot, config.Configure)
// Database
sl.Provide[database.Database](l, database.Slot, &database.Memory{
Users: []model.User{ Users: []model.User{
{ {
Id: "e39ad8d5-a087-4cb2-8fd7-5a6ca3f6a534", Id: "e39ad8d5-a087-4cb2-8fd7-5a6ca3f6a534",
@ -43,21 +49,23 @@ func main() {
}, },
}) })
sl.InjectLazy(l, server.Slot, server.Configure) // Server
sl.InjectLazy(l, listautenti.Slot, listautenti.Configure) sl.ProvideFunc(l, server.Slot, server.Configure)
sl.ProvideHook(l, server.ApiRoutesHook,
listautenti.MountApiRoutesHook,
)
srv, err := sl.Use(l, server.Slot) //
if err != nil { // Start the application
log.Fatal(err) //
}
go func() { sl.MustInvoke(l, server.Slot)
log.Fatal(srv.Router.Listen(cfg.Host))
}()
r, w := io.Pipe() r, w := io.Pipe()
cmd := exec.Command("pnpm", "run", "dev") cfg := sl.MustUse(l, config.Slot)
cmd := exec.Command(cfg.NpmCommand, "run", "dev")
cmd.Stdout = w cmd.Stdout = w
go func() { go func() {

@ -10,18 +10,22 @@ import (
"git.phc.dm.unipi.it/phc/website/server/config" "git.phc.dm.unipi.it/phc/website/server/config"
"git.phc.dm.unipi.it/phc/website/server/database" "git.phc.dm.unipi.it/phc/website/server/database"
"git.phc.dm.unipi.it/phc/website/server/listautenti"
"git.phc.dm.unipi.it/phc/website/server/model" "git.phc.dm.unipi.it/phc/website/server/model"
) )
func main() { func main() {
l := sl.New() l := sl.New()
cfg := sl.InjectValue(l, config.Slot, config.Config{ //
Mode: "production", // Setup the application
Host: ":4000", //
})
// Config
sl.Provide(l, config.Slot, config.ExampleProductionConfig)
sl.InjectValue[database.Database](l, database.Slot, &database.Memory{ // Database
sl.Provide[database.Database](l, database.Slot, &database.Memory{
Users: []model.User{ Users: []model.User{
{ {
Id: "e39ad8d5-a087-4cb2-8fd7-5a6ca3f6a534", Id: "e39ad8d5-a087-4cb2-8fd7-5a6ca3f6a534",
@ -38,10 +42,18 @@ func main() {
}, },
}) })
srv, err := server.Configure(l) // Server
sl.ProvideFunc(l, server.Slot, server.Configure)
sl.ProvideHook(l, server.ApiRoutesHook,
listautenti.MountApiRoutesHook,
)
//
// Start the application
//
err := sl.Invoke(l, server.Slot)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Fatal(srv.Router.Listen(cfg.Host))
} }

@ -1,20 +1,20 @@
// The [sl] package has two main concepts, the [ServiceLocator] itself is the // The [sl] package has two main concepts, the [ServiceLocator] itself is the
// main object that one should pass around through the application. A // main object that one should pass around through the application. A
// [ServiceLocator] has a list of slots that can be filled with // [ServiceLocator] has a list of slots that can be filled with
// [InjectLazy] and [InjectValue] and retrieved with [Use]. Slots should // [ProvideFunc] and [Provide] and retrieved with [Use]. Slots should
// be unique by type and they can only be created with the [NewSlot] function. // be unique by type and they can only be created with the [NewSlot] function.
// //
// The usual way to use this module is to make slots for Go interfaces and // The usual way to use this module is to make slots for Go interfaces and
// then pass implementations using the [InjectValue] and // then pass implementations using the [Provide] and
// [InjectLazy] functions. // [ProvideFunc] functions.
// //
// Services can be of various types: // Services can be of various types:
// - a service with no dependencies can be directly injected inside a // - a service with no dependencies can be directly injected inside a
// ServiceLocator using [InjectValue]. // ServiceLocator using [Provide].
// - a service with dependencies on other service should use // - a service with dependencies on other service should use
// [InjectLazy]. This lets the service to initialize itself when needed // [ProvideFunc]. This lets the service configure itself when needed
// and makes the developer not think about correctly ordering the // and makes the developer not think about correctly ordering the
// initialization of its dependencies // recursive configuration of its dependencies.
// - a service can also be private, in this case the slot for a service // - a service can also be private, in this case the slot for a service
// should be a private field in the service package. This kind of // should be a private field in the service package. This kind of
// services should also provide a way to inject them into a // services should also provide a way to inject them into a
@ -40,8 +40,16 @@ import (
var Logger *log.Logger = log.New(os.Stderr, "[service locator] ", log.Lmsgprefix) var Logger *log.Logger = log.New(os.Stderr, "[service locator] ", log.Lmsgprefix)
// slot is just a "typed" unique "symbol". // slot is just a "typed" unique "symbol".
//
// This must be defined like so and not for example "struct{ typeName string }"
// because we might want to have more slots for the same type.
type slot[T any] *struct{} type slot[T any] *struct{}
type Hook[T any] func(*ServiceLocator, T) error
// hook is just a typed unique symbol
type hook[T any] *struct{}
// NewSlot is the only way to create instances of the slot type. Each instance // NewSlot is the only way to create instances of the slot type. Each instance
// is unique. // is unique.
// //
@ -51,85 +59,110 @@ func NewSlot[T any]() slot[T] {
return slot[T](new(struct{})) return slot[T](new(struct{}))
} }
// slotEntry represents a service that can lazily initialized // NewHook is the only way to create instances of the hook type. Each instance
// (using "createFunc"). Once initialized the instance is kept in the "value" // is unique.
//
// This lets you have a service dispatch an hook
func NewHook[T any]() hook[T] {
return hook[T](new(struct{}))
}
// slotEntry represents a service that can lazily configured
// (using "configureFunc"). Once configured the instance is kept in the "value"
// field and "created" will always be "true". The field "typeName" just for // field and "created" will always be "true". The field "typeName" just for
// debugging purposes. // debugging purposes.
type slotEntry struct { type slotEntry struct {
// typeName is just used for debugging purposes
typeName string typeName string
createFunc func(*ServiceLocator) (any, error) // configureFunc is used by lazily provided slot values to tell how to
created bool // configure them self when required
configureFunc func(*ServiceLocator) (any, error)
// configured tells if this slot is already configured
configured bool
// value for this slot
value any value any
} }
func (s *slotEntry) checkInitialized(l *ServiceLocator) error { // checkConfigured tries to call configure on this slot entry if not already configured
if !s.created { func (s *slotEntry) checkConfigured(l *ServiceLocator) error {
v, err := s.createFunc(l) if !s.configured {
v, err := s.configureFunc(l)
if err != nil { if err != nil {
return err return err
} }
Logger.Printf(`[slot: %s] initialized value of type %T`, s.typeName, v) Logger.Printf(`[slot: %s] configured service of type %T`, s.typeName, v)
s.created = true s.configured = true
s.value = v s.value = v
} }
return nil return nil
} }
type hookEntry struct {
// typeName is just used for debugging purposes
typeName string
// listeners is a list of functions to call when this hook is called
listeners []func(*ServiceLocator, any) error
}
// ServiceLocator is the main context passed around to retrive service // ServiceLocator is the main context passed around to retrive service
// instances, the interface uses generics so to inject and retrive service // instances, the interface uses generics so to inject and retrive service
// instances you should use the functions [InjectValue], [InjectLazy] and // instances you should use the functions [Provide], [ProvideFunc] and [Use].
// [Use]. // This is essentially a dictionary indexed by slots that are them self just
// typed unique symbols
type ServiceLocator struct { type ServiceLocator struct {
providers map[any]*slotEntry providers map[any]*slotEntry
hooks map[any]*hookEntry
} }
// New creates a new [ServiceLocator] context to pass around in the application. // New creates a new [ServiceLocator] context to pass around in the application.
func New() *ServiceLocator { func New() *ServiceLocator {
return &ServiceLocator{ return &ServiceLocator{
providers: map[any]*slotEntry{}, providers: map[any]*slotEntry{},
hooks: map[any]*hookEntry{},
} }
} }
// InjectValue will inject a concrete instance inside the ServiceLocator "l" // Provide will inject a concrete instance inside the ServiceLocator "l" for
// for the given "slotKey". This should be used for injecting "static" // the given "slotKey". This should be used for injecting "static" services, for
// services, for instances whose construction depend on other services you // instances whose construction depend on other services you should use the
// should use the [InjectLazy] function. // [ProvideFunc] function.
// //
// This is generic over "T" to check that instances for the given slot type // This is generic over "T" to check that instances for the given slot type
// check as "T" can also be an interface. // check as "T" can also be an interface.
func InjectValue[T any](l *ServiceLocator, slotKey slot[T], value T) T { func Provide[T any](l *ServiceLocator, slotKey slot[T], value T) T {
Logger.Printf(`[slot: %s] injected value of type %T`, getTypeName[T](), value) typeName := getTypeName[T]()
Logger.Printf(`[slot: %s] provided value of type %T`, typeName, value)
l.providers[slotKey] = &slotEntry{ l.providers[slotKey] = &slotEntry{
getTypeName[T](), typeName: typeName,
nil, configured: true,
true, value: value,
value,
} }
return value return value
} }
// InjectLazy will inject an instance inside the given ServiceLocator // ProvideFunc will inject an instance inside the given ServiceLocator
// and "slotKey" that is created only when requested with a call to the // and "slotKey" that is created only when requested with a call to the
// [Use] function. // [Use] function.
// //
// This is generic over "T" to check that instances for the given slot type // This is generic over "T" to check that instances for the given slot type
// check as "T" can also be an interface. // check as "T" can also be an interface.
func InjectLazy[T any](l *ServiceLocator, slotKey slot[T], createFunc func(*ServiceLocator) (T, error)) { func ProvideFunc[T any](l *ServiceLocator, slotKey slot[T], createFunc func(*ServiceLocator) (T, error)) {
Logger.Printf(`[slot: %s] injected lazy`, getTypeName[T]()) typeName := getTypeName[T]()
Logger.Printf(`[slot: %s] inject lazy provider`, typeName)
l.providers[slotKey] = &slotEntry{ l.providers[slotKey] = &slotEntry{
createFunc: func(l *ServiceLocator) (any, error) { typeName: typeName,
return createFunc(l) configureFunc: func(l *ServiceLocator) (any, error) { return createFunc(l) },
}, configured: false,
created: false,
value: nil,
typeName: getTypeName[T](),
} }
} }
@ -137,7 +170,7 @@ func InjectLazy[T any](l *ServiceLocator, slotKey slot[T], createFunc func(*Serv
// the provided ServiceLocator instance. // the provided ServiceLocator instance.
// //
// If the ServiceLocator does not have a value for the slot key, or if the // If the ServiceLocator does not have a value for the slot key, or if the
// value wasn't correctly initialized (in the case of a lazy slot), an error // value wasn't correctly configured (in the case of a lazy slot), an error
// is returned. // is returned.
func Use[T any](l *ServiceLocator, slotKey slot[T]) (T, error) { func Use[T any](l *ServiceLocator, slotKey slot[T]) (T, error) {
var zero T var zero T
@ -147,7 +180,7 @@ func Use[T any](l *ServiceLocator, slotKey slot[T]) (T, error) {
return zero, fmt.Errorf(`no injected value for type %s`, getTypeName[T]()) return zero, fmt.Errorf(`no injected value for type %s`, getTypeName[T]())
} }
err := slot.checkInitialized(l) err := slot.checkConfigured(l)
if err != nil { if err != nil {
return zero, err return zero, err
} }
@ -158,24 +191,84 @@ func Use[T any](l *ServiceLocator, slotKey slot[T]) (T, error) {
return v, nil return v, nil
} }
func Invoke[T any](l *ServiceLocator, slotKey slot[T]) error { func MustUse[T any](l *ServiceLocator, slotKey slot[T]) T {
v, err := Use(l, slotKey)
if err != nil {
log.Fatal(err)
}
return v
}
func Invoke[T any](l *ServiceLocator, slotKey slot[T]) error {
slot, ok := l.providers[slotKey] slot, ok := l.providers[slotKey]
if !ok { if !ok {
return fmt.Errorf(`no injected value for type %s`, getTypeName[T]()) return fmt.Errorf(`no injected value for type %s`, getTypeName[T]())
} }
err := slot.checkInitialized(l) err := slot.checkConfigured(l)
if err != nil { if err != nil {
return err return err
} }
v := slot.value.(T) v := slot.value.(T)
Logger.Printf(`[slot: %s] using slot with value of type %T`, getTypeName[T](), v) Logger.Printf(`[slot: %s] invoked slot with value of type %T`, getTypeName[T](), v)
return nil
}
func MustInvoke[T any](l *ServiceLocator, slotKey slot[T]) {
if err := Invoke(l, slotKey); err != nil {
log.Fatal(err)
}
}
func ProvideHook[T any](l *ServiceLocator, hookKey hook[T], listeners ...Hook[T]) {
typeName := getTypeName[T]()
Logger.Printf(`[hook: %s] injecting hooks`, typeName)
// cast type safe listeners to internal untyped version to put inside the hook map
anyListeners := make([]func(*ServiceLocator, any) error, len(listeners))
for i, l := range listeners {
ll := l
anyListeners[i] = func(l *ServiceLocator, a any) error {
t, ok := a.(T)
if !ok {
panic(`illegal state`)
}
return ll(l, t)
}
}
l.hooks[hookKey] = &hookEntry{
typeName: typeName,
listeners: anyListeners,
}
}
func UseHook[T any](l *ServiceLocator, hookKey hook[T], value T) error {
hookEntry, ok := l.hooks[hookKey]
if !ok {
return fmt.Errorf(`no injected hooks for hook of type %s`, hookEntry.typeName)
}
Logger.Printf(`[hook: %s] calling hook with value of type %T`, hookEntry.typeName, value)
for _, hookFunc := range hookEntry.listeners {
if err := hookFunc(l, value); err != nil {
return err
}
}
return nil return nil
} }
func MustUseHook[T any](l *ServiceLocator, hookKey hook[T], value T) {
if err := UseHook(l, hookKey, value); err != nil {
log.Fatal(err)
}
}
// getTypeName is a trick to get the name of a type (even if it is an // getTypeName is a trick to get the name of a type (even if it is an
// interface type) // interface type)
func getTypeName[T any]() string { func getTypeName[T any]() string {

@ -9,37 +9,46 @@ import (
type Config struct { type Config struct {
Mode string Mode string
Host string Host string
NpmCommand string
} }
var Slot = sl.NewSlot[Config]() var Slot = sl.NewSlot[Config]()
func setFromEnvOrDefault(target *string, m map[string]string, key string, defaultValue string) {
v, ok := m[key]
if ok {
*target = v
} else {
*target = defaultValue
}
}
func Configure(l *sl.ServiceLocator) (Config, error) { func Configure(l *sl.ServiceLocator) (Config, error) {
m, err := godotenv.Read(".env") env, err := godotenv.Read(".env")
if err != nil { if err != nil {
return Config{}, err return Config{}, err
} }
var cfg Config var cfg Config
cfg.Mode = "production" setFromEnvOrDefault(&cfg.Mode, env, "MODE", "production")
if v, ok := m["MODE"]; ok { setFromEnvOrDefault(&cfg.Host, env, "HOST", ":4000")
cfg.Mode = v setFromEnvOrDefault(&cfg.NpmCommand, env, "NPM_COMMAND", "npm")
}
cfg.Host = ":4000"
if v, ok := m["HOST"]; ok {
cfg.Host = v
}
return cfg, nil return cfg, nil
} }
var TestingProductionConfig = Config{ var ExampleProductionConfig = Config{
Mode: "production", Mode: "production",
Host: ":4000", Host: ":4000",
NpmCommand: "npm",
} }
var TestingDevelopmentConfig = Config{ var ExampleDevelopmentConfig = Config{
Mode: "development", Mode: "development",
Host: ":4000", Host: ":4000",
NpmCommand: "npm",
} }

@ -6,25 +6,15 @@ import (
"git.phc.dm.unipi.it/phc/website/libs/sl" "git.phc.dm.unipi.it/phc/website/libs/sl"
"git.phc.dm.unipi.it/phc/website/server/database" "git.phc.dm.unipi.it/phc/website/server/database"
"git.phc.dm.unipi.it/phc/website/server/routes"
) )
type ListaUtenti struct{} func MountApiRoutesHook(l *sl.ServiceLocator, api fiber.Router) error {
var Slot = sl.NewSlot[*ListaUtenti]()
func Configure(l *sl.ServiceLocator) (*ListaUtenti, error) {
db, err := sl.Use(l, database.Slot) db, err := sl.Use(l, database.Slot)
if err != nil { if err != nil {
return nil, err return err
}
r, err := sl.Use(l, routes.Root)
if err != nil {
return nil, err
} }
r.Get("/api/lista-utenti", func(c *fiber.Ctx) error { api.Get("/lista-utenti", func(c *fiber.Ctx) error {
users, err := db.ReadUsers() users, err := db.ReadUsers()
if err != nil { if err != nil {
return err return err
@ -34,5 +24,5 @@ func Configure(l *sl.ServiceLocator) (*ListaUtenti, error) {
return c.JSON(users) return c.JSON(users)
}) })
return &ListaUtenti{}, nil return nil
} }

@ -10,23 +10,22 @@ import (
"gotest.tools/assert" "gotest.tools/assert"
"github.com/gofiber/fiber/v2"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttputil" "github.com/valyala/fasthttp/fasthttputil"
"git.phc.dm.unipi.it/phc/website/libs/db" "git.phc.dm.unipi.it/phc/website/libs/db"
"git.phc.dm.unipi.it/phc/website/libs/sl" "git.phc.dm.unipi.it/phc/website/libs/sl"
"git.phc.dm.unipi.it/phc/website/libs/util" "git.phc.dm.unipi.it/phc/website/libs/util"
"git.phc.dm.unipi.it/phc/website/server"
"git.phc.dm.unipi.it/phc/website/server/config" "git.phc.dm.unipi.it/phc/website/server/config"
"git.phc.dm.unipi.it/phc/website/server/database" "git.phc.dm.unipi.it/phc/website/server/database"
"git.phc.dm.unipi.it/phc/website/server/listautenti" "git.phc.dm.unipi.it/phc/website/server/listautenti"
"git.phc.dm.unipi.it/phc/website/server/model" "git.phc.dm.unipi.it/phc/website/server/model"
"git.phc.dm.unipi.it/phc/website/server/routes"
) )
func TestApiListaUtenti(t *testing.T) { func TestApiListaUtenti(t *testing.T) {
r := fiber.New()
memDB := &database.Memory{ memDB := &database.Memory{
Users: []model.User{ Users: []model.User{
{ {
@ -45,23 +44,35 @@ func TestApiListaUtenti(t *testing.T) {
} }
l := sl.New() l := sl.New()
sl.InjectValue(l, config.Slot, config.TestingProductionConfig)
sl.InjectValue[database.Database](l, database.Slot, memDB)
sl.InjectValue(l, routes.Root, fiber.Router(r))
listautenti.Configure(l)
// Config
sl.Provide(l, config.Slot, config.ExampleProductionConfig)
// Database
sl.Provide[database.Database](l, database.Slot, memDB)
// Server
sl.ProvideFunc(l, server.Slot, server.Configure)
sl.ProvideHook(l, server.ApiRoutesHook,
listautenti.MountApiRoutesHook,
)
// Initialize server instance
srv, err := sl.Use(l, server.Slot)
assert.NilError(t, err)
//
// Try doing the request // Try doing the request
//
req, err := http.NewRequest("GET", "http://localhost:4000/api/lista-utenti", nil) req, err := http.NewRequest("GET", "http://localhost:4000/api/lista-utenti", nil)
if err != nil { assert.NilError(t, err)
t.Error(err)
}
ln := fasthttputil.NewInmemoryListener() ln := fasthttputil.NewInmemoryListener()
defer ln.Close() defer ln.Close()
go func() { go func() {
err := fasthttp.Serve(ln, r.Handler()) err := fasthttp.Serve(ln, srv.Router.Handler())
if err != nil { if err != nil {
panic(fmt.Errorf("failed to serve: %v", err)) panic(fmt.Errorf("failed to serve: %v", err))
} }
@ -76,27 +87,23 @@ func TestApiListaUtenti(t *testing.T) {
} }
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { assert.NilError(t, err)
t.Error(err)
}
body, err := io.ReadAll(res.Body) body, err := io.ReadAll(res.Body)
if err != nil { assert.NilError(t, err)
t.Error(err)
}
assert.Equal(t, string(body), util.CompactIndentedLines(` assert.Equal(t, string(body), util.CompactIndentedLines(`
[ [
{ {
"Id":"claire", "Id":"e39ad8d5-a087-4cb2-8fd7-5a6ca3f6a534",
"Username":"claire-doe",
"FullName":"Claire Doe", "FullName":"Claire Doe",
"Nickname":"claire-doe",
"Email":"claire.doe@example.org" "Email":"claire.doe@example.org"
}, },
{ {
"Id":"john", "Id":"9b7109cd-95a1-41e9-a9f6-001a32c20ca1",
"Username":"john-smith",
"FullName":"John Smith", "FullName":"John Smith",
"Nickname":"john-smith",
"Email":"john.smith@example.org" "Email":"john.smith@example.org"
} }
] ]

@ -1,9 +0,0 @@
package routes
import (
"git.phc.dm.unipi.it/phc/website/libs/sl"
"github.com/gofiber/fiber/v2"
)
var Root = sl.NewSlot[fiber.Router]()

@ -1,9 +1,10 @@
package server package server
import ( import (
"log"
"git.phc.dm.unipi.it/phc/website/libs/sl" "git.phc.dm.unipi.it/phc/website/libs/sl"
"git.phc.dm.unipi.it/phc/website/server/listautenti" "git.phc.dm.unipi.it/phc/website/server/config"
"git.phc.dm.unipi.it/phc/website/server/routes"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -12,15 +13,26 @@ type Server struct{ Router *fiber.App }
var Slot = sl.NewSlot[*Server]() var Slot = sl.NewSlot[*Server]()
var ApiRoutesHook = sl.NewHook[fiber.Router]()
func Configure(l *sl.ServiceLocator) (*Server, error) { func Configure(l *sl.ServiceLocator) (*Server, error) {
cfg, err := sl.Use(l, config.Slot)
if err != nil {
return nil, err
}
r := fiber.New(fiber.Config{}) r := fiber.New(fiber.Config{})
r.Static("/assets", "./out/frontend/assets") r.Static("/assets", "./out/frontend/assets")
sl.InjectValue(l, routes.Root, fiber.Router(r)) api := r.Group("/api")
if err := sl.UseHook(l, ApiRoutesHook, api); err != nil {
if err := sl.Invoke(l, listautenti.Slot); err != nil {
return nil, err return nil, err
} }
go func() {
log.Fatal(r.Listen(cfg.Host))
}()
return &Server{r}, nil return &Server{r}, nil
} }

Loading…
Cancel
Save