Skip to content

Commit

Permalink
added apis.Serve helper
Browse files Browse the repository at this point in the history
  • Loading branch information
ganigeorgiev committed Apr 20, 2023
1 parent 060ed80 commit 3358d84
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 134 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
## (WIP) v0.15.1
## (WIP)

- Added `apis.Serve(app, options)` helper to allow starting the API server programmatically.


## v0.15.1

- Fixed `Ctrl + S` in the `editor` field not propagating the quick save shortcut to the parent form.

Expand Down
165 changes: 165 additions & 0 deletions apis/serve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package apis

import (
"crypto/tls"
"log"
"net"
"net/http"
"path/filepath"
"strings"
"time"

"github.com/fatih/color"
"github.com/labstack/echo/v5/middleware"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/migrations/logs"
"github.com/pocketbase/pocketbase/tools/migrate"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
)

// ServeOptions defines an optional struct for apis.Serve().
type ServeOptions struct {
ShowStartBanner bool
HttpAddr string
HttpsAddr string
AllowedOrigins []string // optional list of CORS origins (default to "*")
BeforeServeFunc func(server *http.Server) error
}

// Serve starts a new app web server.
func Serve(app core.App, options *ServeOptions) error {
if options == nil {
options = &ServeOptions{}
}

if len(options.AllowedOrigins) == 0 {
options.AllowedOrigins = []string{"*"}
}

// ensure that the latest migrations are applied before starting the server
if err := runMigrations(app); err != nil {
return err
}

// reload app settings in case a new default value was set with a migration
// (or if this is the first time the init migration was executed)
if err := app.RefreshSettings(); err != nil {
color.Yellow("=====================================")
color.Yellow("WARNING: Settings load error! \n%v", err)
color.Yellow("Fallback to the application defaults.")
color.Yellow("=====================================")
}

router, err := InitApi(app)
if err != nil {
return err
}

// configure cors
router.Use(middleware.CORSWithConfig(middleware.CORSConfig{
Skipper: middleware.DefaultSkipper,
AllowOrigins: options.AllowedOrigins,
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
}))

// start http server
// ---
mainAddr := options.HttpAddr
if options.HttpsAddr != "" {
mainAddr = options.HttpsAddr
}

mainHost, _, _ := net.SplitHostPort(mainAddr)

certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
Cache: autocert.DirCache(filepath.Join(app.DataDir(), ".autocert_cache")),
HostPolicy: autocert.HostWhitelist(mainHost, "www."+mainHost),
}

serverConfig := &http.Server{
TLSConfig: &tls.Config{
GetCertificate: certManager.GetCertificate,
NextProtos: []string{acme.ALPNProto},
},
ReadTimeout: 5 * time.Minute,
ReadHeaderTimeout: 30 * time.Second,
// WriteTimeout: 60 * time.Second, // breaks sse!
Handler: router,
Addr: mainAddr,
}

if options.BeforeServeFunc != nil {
if err := options.BeforeServeFunc(serverConfig); err != nil {
return err
}
}

if options.ShowStartBanner {
schema := "http"
if options.HttpsAddr != "" {
schema = "https"
}

date := new(strings.Builder)
log.New(date, "", log.LstdFlags).Print()

bold := color.New(color.Bold).Add(color.FgGreen)
bold.Printf(
"%s Server started at %s\n",
strings.TrimSpace(date.String()),
color.CyanString("%s://%s", schema, serverConfig.Addr),
)

regular := color.New()
regular.Printf(" ➜ REST API: %s\n", color.CyanString("%s://%s/api/", schema, serverConfig.Addr))
regular.Printf(" ➜ Admin UI: %s\n", color.CyanString("%s://%s/_/", schema, serverConfig.Addr))
}

// start HTTPS server
if options.HttpsAddr != "" {
// if httpAddr is set, start an HTTP server to redirect the traffic to the HTTPS version
if options.HttpAddr != "" {
go http.ListenAndServe(options.HttpAddr, certManager.HTTPHandler(nil))
}

return serverConfig.ListenAndServeTLS("", "")
}

// OR start HTTP server
return serverConfig.ListenAndServe()
}

type migrationsConnection struct {
DB *dbx.DB
MigrationsList migrate.MigrationsList
}

func runMigrations(app core.App) error {
connections := []migrationsConnection{
{
DB: app.DB(),
MigrationsList: migrations.AppMigrations,
},
{
DB: app.LogsDB(),
MigrationsList: logs.LogsMigrations,
},
}

for _, c := range connections {
runner, err := migrate.NewRunner(c.DB, c.MigrationsList)
if err != nil {
return err
}

if _, err := runner.Up(); err != nil {
return err
}
}

return nil
}
141 changes: 8 additions & 133 deletions cmd/serve.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
package cmd

import (
"crypto/tls"
"log"
"net"
"net/http"
"path/filepath"
"strings"
"time"

"github.com/fatih/color"
"github.com/labstack/echo/v5/middleware"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/migrations/logs"
"github.com/pocketbase/pocketbase/tools/migrate"
"github.com/spf13/cobra"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
)

// NewServeCommand creates and returns new command responsible for
Expand All @@ -33,96 +20,15 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
Use: "serve",
Short: "Starts the web server (default to 127.0.0.1:8090)",
Run: func(command *cobra.Command, args []string) {
// ensure that the latest migrations are applied before starting the server
if err := runMigrations(app); err != nil {
panic(err)
}

// reload app settings in case a new default value was set with a migration
// (or if this is the first time the init migration was executed)
if err := app.RefreshSettings(); err != nil {
color.Yellow("=====================================")
color.Yellow("WARNING: Settings load error! \n%v", err)
color.Yellow("Fallback to the application defaults.")
color.Yellow("=====================================")
}

router, err := apis.InitApi(app)
if err != nil {
panic(err)
}

// configure cors
router.Use(middleware.CORSWithConfig(middleware.CORSConfig{
Skipper: middleware.DefaultSkipper,
AllowOrigins: allowedOrigins,
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
}))

// start http server
// ---
mainAddr := httpAddr
if httpsAddr != "" {
mainAddr = httpsAddr
}

mainHost, _, _ := net.SplitHostPort(mainAddr)

certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
Cache: autocert.DirCache(filepath.Join(app.DataDir(), ".autocert_cache")),
HostPolicy: autocert.HostWhitelist(mainHost, "www."+mainHost),
}

serverConfig := &http.Server{
TLSConfig: &tls.Config{
GetCertificate: certManager.GetCertificate,
NextProtos: []string{acme.ALPNProto},
},
ReadTimeout: 5 * time.Minute,
ReadHeaderTimeout: 30 * time.Second,
// WriteTimeout: 60 * time.Second, // breaks sse!
Handler: router,
Addr: mainAddr,
}

if showStartBanner {
schema := "http"
if httpsAddr != "" {
schema = "https"
}

date := new(strings.Builder)
log.New(date, "", log.LstdFlags).Print()

bold := color.New(color.Bold).Add(color.FgGreen)
bold.Printf(
"%s Server started at %s\n",
strings.TrimSpace(date.String()),
color.CyanString("%s://%s", schema, serverConfig.Addr),
)
err := apis.Serve(app, &apis.ServeOptions{
HttpAddr: httpAddr,
HttpsAddr: httpsAddr,
ShowStartBanner: showStartBanner,
AllowedOrigins: allowedOrigins,
})

regular := color.New()
regular.Printf(" ➜ REST API: %s\n", color.CyanString("%s://%s/api/", schema, serverConfig.Addr))
regular.Printf(" ➜ Admin UI: %s\n", color.CyanString("%s://%s/_/", schema, serverConfig.Addr))
}

var serveErr error
if httpsAddr != "" {
// if httpAddr is set, start an HTTP server to redirect the traffic to the HTTPS version
if httpAddr != "" {
go http.ListenAndServe(httpAddr, certManager.HTTPHandler(nil))
}

// start HTTPS server
serveErr = serverConfig.ListenAndServeTLS("", "")
} else {
// start HTTP server
serveErr = serverConfig.ListenAndServe()
}

if serveErr != http.ErrServerClosed {
log.Fatalln(serveErr)
if err != http.ErrServerClosed {
log.Fatalln(err)
}
},
}
Expand Down Expand Up @@ -150,34 +56,3 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {

return command
}

type migrationsConnection struct {
DB *dbx.DB
MigrationsList migrate.MigrationsList
}

func runMigrations(app core.App) error {
connections := []migrationsConnection{
{
DB: app.DB(),
MigrationsList: migrations.AppMigrations,
},
{
DB: app.LogsDB(),
MigrationsList: logs.LogsMigrations,
},
}

for _, c := range connections {
runner, err := migrate.NewRunner(c.DB, c.MigrationsList)
if err != nil {
return err
}

if _, err := runner.Up(); err != nil {
return err
}
}

return nil
}

0 comments on commit 3358d84

Please sign in to comment.