Skip to content

Commit

Permalink
publish
Browse files Browse the repository at this point in the history
  • Loading branch information
KilianSen committed Feb 20, 2024
1 parent 29c18e9 commit 0adc3c6
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 1 deletion.
154 changes: 153 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,154 @@
# hookSettings-for-PocketBase
A PocketBase js hook and module to bring config modules to the WebUI
A PocketBase Hook and JS Module to bring [config modules](https://pocketbase.io/docs/js-overview/#handlers-scope) for other JS Hooks to the WebUI

![Collection Image](/images/collection.png)

## Usage

When first run this module creates a new collection `pbHookSettings` within your PocketBase UI.

This collection has two fields `setting` and `value`.
The `setting` field is a string and the `value` field is JSON data.

It's advised to use the module `hookSettings.js` to create a new settings record.

Those methods can be used within the [handler scope](https://pocketbase.io/docs/js-overview/#handlers-scope) to create a new settings record if it does not exist.
```javascript
require('hookSettings.js').Setting('sampleSetting', defaultValue)
```
This will create a new setting in the interface and return a new `Setting` object.

A `Setting` object has the following methods:
- `get()` - Returns the value of the setting
- `set(value)` - Sets the value of the setting

### Tiny example


```javascript
const defaultValue = {
name: 'John Doe', age: 25,
};
// Entry into pbHookSettings collection will automatically be created
const settings = require('hookSettings.js').Setting('settingName', defaultValue);

settings.get().name;
settings.get().age;

settings.set({name: 'Jane Doe', age: 26});
```


# Install

### Standard hooks folder location

1. Download this repository
2. Unzip the downloaded file
3. Copy the `pb_hooks` folder to your PocketBase executable folder
4. Run PocketBase

### Changed hooks folder location

1. Download this repository
2. Unzip the downloaded file
3. Copy the contents of the `pb_hooks` folder to the location you have set in your PocketBase settings
4. Run PocketBase

# Caveats
- It's currently not possible to use settings `onBeforeBootstrap`
- It's currently not possible to use settings in `cronAdd(NOT POSSIBLE HERE, NOT POSSIBLE HERE, () => {POSSIBLE HERE})` since this is executed before the db is running


# Example

This is an example of how to use the `hookSettings.js` module to create a hook that deletes unverified users
after a set time. The time and mail settings are stored in the `pbHookSettings` collection and can be changed
at any time without having to change the code/restarting from the User Interface.

### Exception
It is possible that `deleteUnverified.pb.js` is executed before `hookSettings.pb.js` which will cause an error, since the `pbHookSettings` collection does not exist yet. This can be fixed by renaming `hookSettings.pb.js` to `0_hookSettings.pb.js` and restarting PocketBase.


### Structure
```text
example_folder
├── pb_data
├── pb_migrations
├── pb_hooks
│ ├── hookSettings.js
│ ├── hookSettings.pb.js
│ └── deleteUnverified.pb.js
├── CHANGELOG.md
├── LICENSE.md
└── pocketbase.exe
```

### deleteUnverified.pb.js
```javascript
onAfterBootstrap((e) => {
const config = require(`${__hooks}/hookSettings.js`).Setting("deleteUnverified", {
time: 10, // Time in minutes
mail: true,
mailSubject: "Your account has been deleted",
mailBody: "Your account has been deleted due not being verified within the set time limit."
})

$app.logger().debug("Initialized deleteUnverified.pb.js with time: " + config.get().time + " minutes", "type", "hook",
"file", "deleteUnverified.pb.js")
})

cronAdd("deleteUnverified", "*/" + 2 + " * * * *", () => {
// Default value is empty since the db entry is already created before this code is executed
const config = require(`${__hooks}/hookSettings.js`).Setting("deleteUnverified", {})

const result = arrayOf(new DynamicModel({
"id": "",
"email": "",
}))

$app.dao().db()
.select("id", "email")
.from("users")
.andWhere($dbx.hashExp({verified: false}))
.all(result)


result.forEach((row) => {
const e = $app.dao().findRecordById("users", row?.id);
const s = $app.dao().findRecordById("users", row?.id).getCreated();
if (s && (new Date().getTime() - s.time().unixMilli()) > config.get().time * 60 * 1000) {
$app.logger().info("CLEAN Deleting aged unverified user " + row?.id + " with mail " + row?.email, "type", "hook",
"file", "deleteUnverified.pb.js")
$app.dao().deleteRecord(e)

if (!config.get().mail) {
return
}

function replaceTemplates(value) {
value = value
.replace(/{id}/g, e.id)
.replace(/{email}/g, e.email())
.replace(/{username}/g, e.username())
.replace(/{verified}/g, e.verified())
return value
}

const message = new MailerMessage({
from: {
address: $app.settings().meta.senderAddress,
name: $app.settings().meta.senderName,
},
to: [{address: e.email()}],
subject: replaceTemplates(config.get().mailSubject),
html: replaceTemplates(config.get().mailBody),
})

$app.newMailClient().send(message)
}
})
})
```
![Example Image](/images/WithEntry.png)
Binary file added images/Collection.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Plain.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/WithEntry.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions pb_hooks/hookSettings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module.exports = {
Setting: (setting, defaultValue) => {
class HookSetting {
constructor(setting, defaultValue) {
this.setting = setting
this.defaultValue = defaultValue

try {
if (this.recordNotExists()) {
this.set(this.defaultValue)
}
} catch (unused) {
console.log("------------------")
console.log("")
console.log("An error occurred in hookSettings.js!")
console.log("A possible cause of this is that the pbHookSettings collection does not yet exist.")
console.log("This can happen if hookSettings.js is called before hookSettings.pb.js (only needs")
console.log("to be called once before hookSettings.js). There are two ways to fix this:")
console.log("1. Try to start PocketBase until hookSettings.pb.js is called before hookSettings.js")
console.log("2. (recommended) Temporarily rename hookSettings.pb.js to hookSettings.pb.js")
console.log("3. (not recommended) Manually create the pbHookSettings collection in the database")
console.log("")
console.log("------------------")
}
}


possibleRecords() {
return $app.dao().findRecordsByExpr("pbHookSettings", $dbx.hashExp({setting: this.setting}))
}

recordNotExists() {
return this.possibleRecords().length === 0
}

get() {
let result = this.possibleRecords()
if (result.length > 0) {
return JSON.parse(result[0].get("state"))
}
return this.defaultValue
}

set(value) {
if (this.recordNotExists()) {
let record = new Record($app.dao().findCollectionByNameOrId("pbHookSettings"), {
setting: this.setting,
state: defaultValue
})
$app.dao().saveRecord(record)
}
let result = $app.dao().findRecordsByExpr("pbHookSettings", $dbx.hashExp({setting: this.setting}))[0]
result.set("state", JSON.stringify(value))
$app.dao().saveRecord(result)
}
}

return new HookSetting(setting, defaultValue)
}
}
46 changes: 46 additions & 0 deletions pb_hooks/hookSettings.pb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
onAfterBootstrap((e) => {
/// Initialize the hook settings collection
/// I know using a try catch block is not the best way to do this, but it works for now
$app.logger().debug(
"Initializing hookSettings.pb.js",
"type", "hook",
"file", "hookSettings.pb.js"
)
try {
/// Try to find the collection (fails if it doesn't exist)
!$app.dao().findCollectionByNameOrId("pbHookSettings")
$app.logger().debug(
"Hook settings collection found, skipping initialization",
"type", "hook",
"file", "hookSettings.pb.js"
)
} catch (ignored) {
/// create collection
$app.logger().info(
"Hook settings collection not found, creating!",
"type", "hook",
"file", "hookSettings.pb.js"
)
const form = new CollectionUpsertForm($app, new Collection())
form.name = "pbHookSettings"
form.type = "base"
form.schema.addField(new SchemaField({
name: "setting",
type: "text",
required: true,
presentable: true,
options: {
maxSize: 999,
}
}))
form.schema.addField(new SchemaField({
name: "state",
type: "json",
required: true,
options: {
maxSize: 999
}
}))
form.submit()
}
})

0 comments on commit 0adc3c6

Please sign in to comment.