diff --git a/Makefile b/Makefile index 5418e1a3..8c32c28e 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,18 @@ +start: examples/base/pocketbase + examples/base/pocketbase serve --dir examples/base/data + +examples/base/pocketbase: **/*.go ui/dist lubinas/dist + go build -o examples/base/pocketbase examples/base/main.go + +ui/dist: ui/node_modules ui/vite.config.js ui/index.html ui/src/**/* ui/public/**/* + npm --prefix ui run build + +ui/node_modules: ui/package.json + npm --prefix ui install + +clean: + rm -rf ui/node_modules examples/base/pocketbase + lint: golangci-lint run -c ./golangci.yml ./... diff --git a/examples/base/main.go b/examples/base/main.go index 1ea529b4..add6acdf 100644 --- a/examples/base/main.go +++ b/examples/base/main.go @@ -3,129 +3,51 @@ package main import ( "log" "net/http" - "os" - "path/filepath" "github.com/pocketbase/pocketbase" - "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" - "github.com/pocketbase/pocketbase/plugins/ghupdate" - "github.com/pocketbase/pocketbase/plugins/jsvm" - "github.com/pocketbase/pocketbase/plugins/migratecmd" - "github.com/pocketbase/pocketbase/tools/hook" - "github.com/pocketbase/pocketbase/tools/osutils" ) func main() { app := pocketbase.New() - // --------------------------------------------------------------- - // Optional plugin flags: - // --------------------------------------------------------------- + app.OnRecordViewRequest().BindFunc(addLastModified) - var hooksDir string - app.RootCmd.PersistentFlags().StringVar( - &hooksDir, - "hooksDir", - "", - "the directory with the JS app hooks", - ) + app.OnRecordUpdateRequest().BindFunc(ifUnmodifiedSince) - var hooksWatch bool - app.RootCmd.PersistentFlags().BoolVar( - &hooksWatch, - "hooksWatch", - true, - "auto restart the app on pb_hooks file change; it has no effect on Windows", - ) - - var hooksPool int - app.RootCmd.PersistentFlags().IntVar( - &hooksPool, - "hooksPool", - 15, - "the total prewarm goja.Runtime instances for the JS app hooks execution", - ) - - var migrationsDir string - app.RootCmd.PersistentFlags().StringVar( - &migrationsDir, - "migrationsDir", - "", - "the directory with the user defined migrations", - ) - - var automigrate bool - app.RootCmd.PersistentFlags().BoolVar( - &automigrate, - "automigrate", - true, - "enable/disable auto migrations", - ) - - var publicDir string - app.RootCmd.PersistentFlags().StringVar( - &publicDir, - "publicDir", - defaultPublicDir(), - "the directory to serve static files", - ) - - var indexFallback bool - app.RootCmd.PersistentFlags().BoolVar( - &indexFallback, - "indexFallback", - true, - "fallback the request to index.html on missing static path, e.g. when pretty urls are used with SPA", - ) - - app.RootCmd.ParseFlags(os.Args[1:]) - - // --------------------------------------------------------------- - // Plugins and hooks: - // --------------------------------------------------------------- - - // load jsvm (pb_hooks and pb_migrations) - jsvm.MustRegister(app, jsvm.Config{ - MigrationsDir: migrationsDir, - HooksDir: hooksDir, - HooksWatch: hooksWatch, - HooksPoolSize: hooksPool, - }) - - // migrate command (with js templates) - migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{ - TemplateLang: migratecmd.TemplateLangJS, - Automigrate: automigrate, - Dir: migrationsDir, - }) - - // GitHub selfupdate - ghupdate.MustRegister(app, app.RootCmd, ghupdate.Config{}) - - // static route to serves files from the provided public dir - // (if publicDir exists and the route path is not already defined) - app.OnServe().Bind(&hook.Handler[*core.ServeEvent]{ - Func: func(e *core.ServeEvent) error { - if !e.Router.HasRoute(http.MethodGet, "/{path...}") { - e.Router.GET("/{path...}", apis.Static(os.DirFS(publicDir), indexFallback)) - } - - return e.Next() - }, - Priority: 999, // execute as latest as possible to allow users to provide their own route - }) + app.OnRecordDeleteRequest().BindFunc(ifUnmodifiedSince) if err := app.Start(); err != nil { log.Fatal(err) } } -// the default pb_public dir location is relative to the executable -func defaultPublicDir() string { - if osutils.IsProbablyGoRun() { - return "./pb_public" +func addLastModified(e *core.RecordRequestEvent) error { + updated := e.Record.GetString("updated") + + if updated != "" { + e.Response.Header().Add("Last-Modified", updated) } - return filepath.Join(os.Args[0], "../pb_public") + return e.Next() +} + +func ifUnmodifiedSince(e *core.RecordRequestEvent) error { + updated := e.Record.GetString("updated") + + if updated != "" { + header := e.Request.Header.Get("If-Unmodified-Since") + + if header == "" || header != updated { + e.Response.Header().Add("Last-Modified", updated) + + if header == "" { + return e.Error(http.StatusPreconditionRequired, "Header If-Unmodified-Since is required", nil) + } else if header != updated { + return e.Error(http.StatusPreconditionFailed, "Record was modified after retrieval", nil) + } + } + } + + return e.Next() } diff --git a/ui/src/components/records/RecordUpsertPanel.svelte b/ui/src/components/records/RecordUpsertPanel.svelte index 7bc9a992..15985d63 100644 --- a/ui/src/components/records/RecordUpsertPanel.svelte +++ b/ui/src/components/records/RecordUpsertPanel.svelte @@ -278,7 +278,11 @@ if (isNew) { result = await ApiClient.collection(collection.id).create(data); } else { - result = await ApiClient.collection(collection.id).update(record.id, data); + result = await ApiClient.collection(collection.id).update(record.id, data, { + headers: { + "If-Unmodified-Since": record.updated, + }, + }); } addSuccessToast(isNew ? "Successfully created record." : "Successfully updated record."); @@ -318,7 +322,11 @@ confirm(`Do you really want to delete the selected record?`, () => { return ApiClient.collection(original.collectionId) - .delete(original.id) + .delete(original.id, { + headers: { + "If-Unmodified-Since": record.updated, + }, + }) .then(() => { forceHide(); addSuccessToast("Successfully deleted record.");