mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-01-09 10:07:17 +02:00
132 lines
3.5 KiB
Go
132 lines
3.5 KiB
Go
// Package apis implements the default PocketBase api services and middlewares.
|
|
package apis
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/labstack/echo/v5"
|
|
"github.com/labstack/echo/v5/middleware"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/pocketbase/pocketbase/tools/rest"
|
|
"github.com/pocketbase/pocketbase/ui"
|
|
)
|
|
|
|
// InitApi creates a configured echo instance with registered
|
|
// system and app specific routes and middlewares.
|
|
func InitApi(app core.App) (*echo.Echo, error) {
|
|
e := echo.New()
|
|
e.Debug = app.IsDebug()
|
|
|
|
// default middlewares
|
|
e.Pre(middleware.RemoveTrailingSlash())
|
|
e.Use(middleware.Recover())
|
|
e.Use(middleware.Secure())
|
|
e.Use(LoadAuthContext(app))
|
|
|
|
// custom error handler
|
|
e.HTTPErrorHandler = func(c echo.Context, err error) {
|
|
if c.Response().Committed {
|
|
return
|
|
}
|
|
|
|
var apiErr *rest.ApiError
|
|
|
|
switch v := err.(type) {
|
|
case (*echo.HTTPError):
|
|
if v.Internal != nil && app.IsDebug() {
|
|
log.Println(v.Internal)
|
|
}
|
|
msg := fmt.Sprintf("%v", v.Message)
|
|
apiErr = rest.NewApiError(v.Code, msg, v)
|
|
case (*rest.ApiError):
|
|
if app.IsDebug() && v.RawData() != nil {
|
|
log.Println(v.RawData())
|
|
}
|
|
apiErr = v
|
|
default:
|
|
if err != nil && app.IsDebug() {
|
|
log.Println(err)
|
|
}
|
|
apiErr = rest.NewBadRequestError("", err)
|
|
}
|
|
|
|
// Send response
|
|
var cErr error
|
|
if c.Request().Method == http.MethodHead {
|
|
// @see https://github.com/labstack/echo/issues/608
|
|
cErr = c.NoContent(apiErr.Code)
|
|
} else {
|
|
cErr = c.JSON(apiErr.Code, apiErr)
|
|
}
|
|
|
|
// truly rare case; eg. client already disconnected
|
|
if cErr != nil && app.IsDebug() {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
|
|
// serves /ui/dist/index.html file
|
|
// (explicit route is used to avoid conflicts with `RemoveTrailingSlash` middleware)
|
|
e.FileFS("/_", "index.html", ui.DistIndexHTML, middleware.Gzip())
|
|
|
|
// serves static files from the /ui/dist directory
|
|
// (similar to echo.StaticFS but with gzip middleware enabled)
|
|
e.GET("/_/*", StaticDirectoryHandler(ui.DistDirFS, false), middleware.Gzip())
|
|
|
|
// default routes
|
|
api := e.Group("/api")
|
|
BindSettingsApi(app, api)
|
|
BindAdminApi(app, api)
|
|
BindUserApi(app, api)
|
|
BindCollectionApi(app, api)
|
|
BindRecordApi(app, api)
|
|
BindFileApi(app, api)
|
|
BindRealtimeApi(app, api)
|
|
BindLogsApi(app, api)
|
|
|
|
// trigger the custom BeforeServe hook for the created api router
|
|
// allowing users to further adjust its options or register new routes
|
|
serveEvent := &core.ServeEvent{
|
|
App: app,
|
|
Router: e,
|
|
}
|
|
if err := app.OnBeforeServe().Trigger(serveEvent); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// catch all any route
|
|
api.Any("/*", func(c echo.Context) error {
|
|
return echo.ErrNotFound
|
|
}, ActivityLogger(app))
|
|
|
|
return e, nil
|
|
}
|
|
|
|
// StaticDirectoryHandler is similar to `echo.StaticDirectoryHandler`
|
|
// but without the directory redirect which conflicts with RemoveTrailingSlash middleware.
|
|
//
|
|
// @see https://github.com/labstack/echo/issues/2211
|
|
func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
p := c.PathParam("*")
|
|
if !disablePathUnescaping { // when router is already unescaping we do not want to do is twice
|
|
tmpPath, err := url.PathUnescape(p)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unescape path variable: %w", err)
|
|
}
|
|
p = tmpPath
|
|
}
|
|
|
|
// fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
|
|
name := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(p, "/")))
|
|
|
|
return c.FileFS(name, fileSystem)
|
|
}
|
|
}
|