diff --git a/gitbook/SUMMARY.md b/gitbook/SUMMARY.md index 4b7e3a5a..ea9605ec 100644 --- a/gitbook/SUMMARY.md +++ b/gitbook/SUMMARY.md @@ -18,6 +18,7 @@ * [FTP Access](usage/ftp.md) * [Backup & Restore](usage/backup-and-restore.md) * [Cloud Functions](usage/cloud.md) +* [PocketBase Hooks](usage/hooks.md) * [Upgrading](usage/upgrading.md) ## Contributing diff --git a/gitbook/usage/ftp.md b/gitbook/usage/ftp.md index 09478ae7..9d486f36 100644 --- a/gitbook/usage/ftp.md +++ b/gitbook/usage/ftp.md @@ -15,6 +15,7 @@ Inside each instance directory, you will find: | pb_data | The PocketBase data directory | | pb_static | The location from which static files are served | | pb_migrations | The PocketBase [migrations](https://pocketbase.io/docs/migrations/) directory | +| pb_hooks | The PocketBase [hooks](https://pocketbase.io/docs/js-overview/) directory | | worker | The Deno-based cloud worker | ## Recommended Clients diff --git a/gitbook/usage/hooks.md b/gitbook/usage/hooks.md new file mode 100644 index 00000000..edea7209 --- /dev/null +++ b/gitbook/usage/hooks.md @@ -0,0 +1,165 @@ +# PocketBase Hooks + +The prebuilt PocketBase v0.17+ executable comes with embedded ES5 JavaScript engine (goja) which enables you to write custom server-side code using plain JavaScript. + +Every Pockethost instance comes with a `pb_hooks` directory which is mounted into the PocketBase instance at `/pb_hooks`. This directory is where you can place your custom server-side code. + +For more information on PocketBase hooks, see the [PocketBase documentation](https://pocketbase.io/docs/js-overview/). + +## Quickstart + +You can start by creating `*.pb.js` file(s) inside the `pb_hooks` directory. The `*.pb.js` files are automatically loaded and executed by PocketBase. + +## Code Samples + +These are only a few simple and limited examples of what you can do with PocketBase hooks. There is lot more available to you. For more information, see the [PocketBase documentation](https://pocketbase.io/docs/js-overview/). + +**Listen to the `onAfterBootstrap` hook handler and log a message to the console. This sample also shows how you can import utils or configs from another file.** +``` +// pb_hooks/main.pb.js + +// This runs when the PocketBase instance is first bootstrapped +onAfterBootstrap((e) => { + // You can load config or util files for your app + const config = require(`${__hooks}/config/config.js`); + const name = 'Hooks!' + const fxTest = config.hello(name); + + console.log("App initialized!"); + console.log(`fxTest: ${fxTest}`); + console.log(`App name: ${JSON.stringify(config)}`); +}); +``` + +``` +// pb_hooks/config/config.js + +module.exports = { + appName: "pockethost-test", + appVersion: "0.0.1", + hello: (name) => { + return "Hello " + name + } +}; +``` + +**Register a new route that you can interface with using the SDK** +``` +// pb_hooks/somefile.pb.js +routerAdd("POST", "/test/:testId", (c) => { + const testId = c.pathParam("testId"); + + return c.json(200, { + testId + }); +}); +``` + +You can now interface with this route using the PocketBase SDK. Here's a JS SDK example: +``` +const pb = new PocketBase('http://127.0.0.1:8090'); + +const response = await pb.send("/test/theTestId", { + method: "POST" +}); + +// response returns { testId: "theTestId" } +``` + +**Find a record and update it** +``` +// pb_hooks/posts.update.pb.js +routerAdd("PATCH", "/posts/:postId", (c) => { + const postId = c.pathParam("postId"); + + // Get body data + const body = $apis.requestInfo(c).data; + const status = body.status; + + // Find a record by ID on the "posts" collection + const record = $app.dao().findRecordById("posts", postId); + + // If the record doesn't exist, return a 404 + // Perhaps you can return a 40X if the user doesn't have permission to update the record etc + if (!record) { + return c.json(404, { + error: "Record not found" + }); + } + + // Update the record with the new status + record.set("status", status); + + // Save the record + $app.dao().saveRecord(record); + + // Expand record before we return it + $app.dao().expandRecord(record, ["user", "comments"], null); + + // Return the record + return c.json(200, { + record + }); +}); +``` + +**Find a collection and create a new record** +``` +// pb_hooks/posts.create.pb.js +routerAdd("POST", "/posts", (c) => { + // Get body data + const body = $apis.requestInfo(c).data; + + // Get values from body + const { postTitle, postDescription } = body; + + // Find the collection by name + const postsCollection = $app.dao().findCollectionByNameOrId("posts"); + + // Create a new post record + const record = new Record(postsCollection, { + title: postTitle, + description: postDescription + }); + + // Save the record + $app.dao().saveRecord(record); + + // Return the record + return c.json(200, { + record + }); +}); +``` + +**Listen to record changes on a collection** +In this sample, we react to a user being registered and we create a new customer record in Stripe. +This is not tested, and is a fictional scenario. But the concept behind it is what we're trying to show. +``` +// pb_hooks/users.onRegister.pb.js + +onRecordAfterCreateRequest((e) => { + // Get the record + const record = e.record; + + try { + // Invoke Stripe API to create a new customer + const response = $http.send({ + url: "https://api.stripe.com/v1/customers", // Stripe API URL + method: "POST", + body: { + email: record.email + }, + headers: { + // Provide Stripe API key or whatever else they require + } + }) + + if (response) { + console.log("Stripe customer created!", response.newCustomerId); + } + } catch (err) { + console.log(err) + } +}, "users") // This runs when a record is created on the "users" collection +``` \ No newline at end of file diff --git a/packages/daemon/src/services/FtpService/FtpService.ts b/packages/daemon/src/services/FtpService/FtpService.ts index 3a8e69ba..ea0fe5b3 100644 --- a/packages/daemon/src/services/FtpService/FtpService.ts +++ b/packages/daemon/src/services/FtpService/FtpService.ts @@ -18,6 +18,7 @@ export enum FolderNames { PbData = 'pb_data', PbStatic = 'pb_static', PbMigrations = 'pb_migrations', + PbHooks = 'pb_hooks', PbWorker = 'worker', } @@ -26,6 +27,7 @@ export const INSTANCE_ROOT_FOLDER_NAMES: FolderNames[] = [ FolderNames.PbStatic, FolderNames.PbWorker, FolderNames.PbMigrations, + FolderNames.PbHooks, ] export function isInstanceRootFolder(name: string): name is FolderNames { diff --git a/packages/pockethost.io/src/routes/app/instances/[instanceId]/Ftpx.svelte b/packages/pockethost.io/src/routes/app/instances/[instanceId]/Ftpx.svelte index b951beb8..241863ee 100644 --- a/packages/pockethost.io/src/routes/app/instances/[instanceId]/Ftpx.svelte +++ b/packages/pockethost.io/src/routes/app/instances/[instanceId]/Ftpx.svelte @@ -32,6 +32,9 @@ pb_migrationsThe PocketBase migrations directory + + pb_hooksThe PocketBase hooks directory + workerDeno worker (cloud TS/JS functions)