123 lines
3.3 KiB
Go
123 lines
3.3 KiB
Go
// Package api configures an http server for administration and application resources.
|
|
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dhax/go-base/api/admin"
|
|
"github.com/dhax/go-base/api/app"
|
|
"github.com/dhax/go-base/auth/jwt"
|
|
"github.com/dhax/go-base/auth/pwdless"
|
|
"github.com/dhax/go-base/database"
|
|
"github.com/dhax/go-base/email"
|
|
"github.com/dhax/go-base/logging"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/go-chi/cors"
|
|
"github.com/go-chi/render"
|
|
)
|
|
|
|
// New configures application resources and routes.
|
|
func New(enableCORS bool) (*chi.Mux, error) {
|
|
logger := logging.NewLogger()
|
|
|
|
db, err := database.DBConn()
|
|
if err != nil {
|
|
logger.WithField("module", "database").Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
mailer, err := email.NewMailer()
|
|
if err != nil {
|
|
logger.WithField("module", "email").Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
authStore := database.NewAuthStore(db)
|
|
authResource, err := pwdless.NewResource(authStore, mailer)
|
|
if err != nil {
|
|
logger.WithField("module", "auth").Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
adminAPI, err := admin.NewAPI(db)
|
|
if err != nil {
|
|
logger.WithField("module", "admin").Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
appAPI, err := app.NewAPI(db)
|
|
if err != nil {
|
|
logger.WithField("module", "app").Error(err)
|
|
return nil, err
|
|
}
|
|
|
|
r := chi.NewRouter()
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(middleware.RequestID)
|
|
// r.Use(middleware.RealIP)
|
|
r.Use(middleware.Timeout(15 * time.Second))
|
|
|
|
r.Use(logging.NewStructuredLogger(logger))
|
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
|
|
|
// use CORS middleware if client is not served by this api, e.g. from other domain or CDN
|
|
if enableCORS {
|
|
r.Use(corsConfig().Handler)
|
|
}
|
|
|
|
r.Mount("/auth", authResource.Router())
|
|
r.Group(func(r chi.Router) {
|
|
r.Use(authResource.TokenAuth.Verifier())
|
|
r.Use(jwt.Authenticator)
|
|
r.Mount("/admin", adminAPI.Router())
|
|
r.Mount("/api", appAPI.Router())
|
|
})
|
|
|
|
r.Get("/ping", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Write([]byte("pong"))
|
|
})
|
|
|
|
client := "./public"
|
|
r.Get("/*", SPAHandler(client))
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func corsConfig() *cors.Cors {
|
|
// Basic CORS
|
|
// for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing
|
|
return cors.New(cors.Options{
|
|
// AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
|
|
AllowedOrigins: []string{"*"},
|
|
// AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
|
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
|
ExposedHeaders: []string{"Link"},
|
|
AllowCredentials: true,
|
|
MaxAge: 86400, // Maximum value not ignored by any of major browsers
|
|
})
|
|
}
|
|
|
|
// SPAHandler serves the public Single Page Application.
|
|
func SPAHandler(publicDir string) http.HandlerFunc {
|
|
handler := http.FileServer(http.Dir(publicDir))
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
indexPage := path.Join(publicDir, "index.html")
|
|
serviceWorker := path.Join(publicDir, "service-worker.js")
|
|
|
|
requestedAsset := path.Join(publicDir, r.URL.Path)
|
|
if strings.Contains(requestedAsset, "service-worker.js") {
|
|
requestedAsset = serviceWorker
|
|
}
|
|
if _, err := os.Stat(requestedAsset); err != nil {
|
|
http.ServeFile(w, r, indexPage)
|
|
return
|
|
}
|
|
handler.ServeHTTP(w, r)
|
|
}
|
|
}
|