chore(pocketbase): track live quotes hook and add git-based hook sync
quotes.pb.js is copied byte-for-byte from the live WFDASH PocketBase. DEPLOY lists the hooks the VPS cron syncs into the hooks volume; the audit hooks stay undeployed for now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
810f0d7350
commit
6b99f52dac
5 changed files with 172 additions and 2 deletions
|
|
@ -135,8 +135,11 @@ Read side: `src/lib/server/audit-log-service.ts` →
|
|||
Noisy fields (search/AI/timers) are skipped via `IGNORED_FIELDS` in the
|
||||
hook utils — add new auto-updated fields there.
|
||||
|
||||
The hook files are deployed by hand into the PB server's `pb_hooks/`
|
||||
(like select values, it's a manual step on the live instance).
|
||||
Hooks deploy from git: a VPS cron syncs the files listed in
|
||||
`pocketbase/pb_hooks/DEPLOY` from `main` into the live PB hooks volume
|
||||
(see `scripts/pb-hooks/README.md`). **As of 2026-10-17 only `quotes.pb.js`
|
||||
is deployed — the audit hooks are NOT live yet**, so `audit_log` is empty
|
||||
until both `audit_log.pb.js` and `audit_log_utils.js` are added to `DEPLOY`.
|
||||
|
||||
# Giellie agent (`/giellie`) — tools come from the MCP, policy lives in one file
|
||||
|
||||
|
|
|
|||
8
pocketbase/pb_hooks/DEPLOY
Normal file
8
pocketbase/pb_hooks/DEPLOY
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Hook files the VPS sync copies into the live WFDASH PocketBase.
|
||||
# One filename per line (relative to this folder). Anything not listed here
|
||||
# is NOT deployed, and a *.js file on the server that is not listed here is
|
||||
# removed on the next sync. See scripts/pb-hooks/README.md.
|
||||
#
|
||||
# Not deployed yet: audit_log.pb.js + audit_log_utils.js — add BOTH lines
|
||||
# together when the audit trail is switched on (utils is required by the hook).
|
||||
quotes.pb.js
|
||||
63
pocketbase/pb_hooks/quotes.pb.js
Executable file
63
pocketbase/pb_hooks/quotes.pb.js
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
onRecordCreate((e) => {
|
||||
if (e.record.getString("quote_number") !== "") {
|
||||
e.next();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var now = new Date();
|
||||
var year = now.getFullYear();
|
||||
var month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
var prefix = "Q" + year + month;
|
||||
|
||||
let attempts = 0;
|
||||
let candidate;
|
||||
|
||||
do {
|
||||
// findRecordsByFilter accepts sort as the 3rd arg.
|
||||
// findFirstRecordByFilter's 3rd arg is params (dbx.Params),
|
||||
// which is why the old "-quote_number" sort string threw
|
||||
// "could not convert -quote_number to dbx.Params".
|
||||
const records = $app.findRecordsByFilter(
|
||||
"quotes",
|
||||
"quote_number != ''",
|
||||
"-quote_number",
|
||||
1,
|
||||
0
|
||||
);
|
||||
|
||||
let newNum = 1;
|
||||
if (records && records.length > 0) {
|
||||
var raw = records[0].getString("quote_number");
|
||||
// Extract trailing digits (handles Q202607011,
|
||||
// legacy Q-2026-07-011, Q1782862717620, etc.).
|
||||
var m = raw.match(/(\d+)$/);
|
||||
if (m) {
|
||||
newNum = parseInt(m[1], 10) + 1 + attempts;
|
||||
}
|
||||
}
|
||||
|
||||
candidate = prefix + String(newNum).padStart(3, '0');
|
||||
attempts++;
|
||||
|
||||
// check if this candidate already exists
|
||||
var exists;
|
||||
try {
|
||||
exists = $app.findFirstRecordByFilter(
|
||||
"quotes",
|
||||
"quote_number = {:num}",
|
||||
{ num: candidate }
|
||||
);
|
||||
} catch (e) {
|
||||
exists = null; // not found throws in some versions
|
||||
}
|
||||
} while (exists && attempts < 5);
|
||||
|
||||
e.record.set("quote_number", candidate);
|
||||
} catch (err) {
|
||||
console.error("Auto-numbering error:", err);
|
||||
e.record.set("quote_number", "Q" + Date.now()); // fallback that's at least unique
|
||||
}
|
||||
|
||||
e.next();
|
||||
}, "quotes");
|
||||
24
scripts/pb-hooks/README.md
Normal file
24
scripts/pb-hooks/README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# PocketBase hooks sync (WFDASH)
|
||||
|
||||
The live WFDASH PocketBase (Coolify service `pocketbase-wfdash`, container
|
||||
`pocketbase-gnqc88suy8fqk6wyryjjdpvl`, PocketBase 0.39.8) loads its hooks
|
||||
from the Docker volume `gnqc88suy8fqk6wyryjjdpvl_pocketbase-hooks`, mounted
|
||||
at `/app/pb_hooks`.
|
||||
|
||||
Hooks are deployed from git, not edited on the server:
|
||||
|
||||
1. Edit or add files in `pocketbase/pb_hooks/` in this repo.
|
||||
2. List every file to deploy in `pocketbase/pb_hooks/DEPLOY`.
|
||||
3. Push to `main` on Forgejo.
|
||||
|
||||
Within ~2 minutes the VPS cron runs `sync-pb-hooks.sh`, which pulls `main`
|
||||
into `/opt/wfdash-pb-hooks/repo` (sparse clone, read-only deploy key) and
|
||||
copies the listed files into the volume. PocketBase reloads itself when a hook
|
||||
file changes. A `.js` file on the server that isn't in `DEPLOY` is removed.
|
||||
|
||||
- Log: `/var/log/wfdash-pb-hooks-sync.log` on the VPS.
|
||||
- Force a sync: `/opt/wfdash-pb-hooks/repo/scripts/pb-hooks/sync-pb-hooks.sh --force`
|
||||
- Roll back: revert the commit (or remove the line from `DEPLOY`) and push.
|
||||
- Pause syncing: comment out the cron line (`crontab -e` as root).
|
||||
|
||||
The sync never touches `pb_data` (the database).
|
||||
72
scripts/pb-hooks/sync-pb-hooks.sh
Executable file
72
scripts/pb-hooks/sync-pb-hooks.sh
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
#!/bin/sh
|
||||
# Sync the WFDASH PocketBase hooks from git into the live hooks volume.
|
||||
#
|
||||
# Runs on the VPS from cron. Pulls origin/main into a sparse clone and copies
|
||||
# every file listed in pocketbase/pb_hooks/DEPLOY into the PocketBase hooks
|
||||
# volume. PocketBase reloads itself when a file in pb_hooks changes, so no
|
||||
# container restart is needed. Files are only written when their content
|
||||
# changed, so an unchanged repo never triggers a reload.
|
||||
#
|
||||
# Usage: sync-pb-hooks.sh [--force] (--force re-syncs even if HEAD is current)
|
||||
set -eu
|
||||
|
||||
REPO="${PB_HOOKS_REPO:-/opt/wfdash-pb-hooks/repo}"
|
||||
TARGET="${PB_HOOKS_TARGET:-/var/lib/docker/volumes/gnqc88suy8fqk6wyryjjdpvl_pocketbase-hooks/_data}"
|
||||
LOCK="${PB_HOOKS_LOCK:-/tmp/wfdash-pb-hooks-sync.lock}"
|
||||
|
||||
log() { echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) $*"; }
|
||||
|
||||
exec 9>"$LOCK"
|
||||
if ! flock -n 9; then
|
||||
log "another sync is running; skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$REPO"
|
||||
git fetch --quiet origin main
|
||||
if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ] && [ "${1:-}" != "--force" ]; then
|
||||
exit 0
|
||||
fi
|
||||
git reset --quiet --hard origin/main
|
||||
|
||||
SRC="$REPO/pocketbase/pb_hooks"
|
||||
LIST="$SRC/DEPLOY"
|
||||
if [ ! -f "$LIST" ]; then
|
||||
log "no $LIST; nothing deployed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate the whole list before touching the live folder.
|
||||
WANTED=$(grep -v '^[[:space:]]*#' "$LIST" | sed 's/[[:space:]]*$//' | grep -v '^$' || true)
|
||||
for f in $WANTED; do
|
||||
case "$f" in
|
||||
*/*|.*) log "refusing unsafe name in DEPLOY: $f"; exit 1 ;;
|
||||
esac
|
||||
if [ ! -f "$SRC/$f" ]; then
|
||||
log "listed file missing in repo: $f — nothing changed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
changed=0
|
||||
for f in $WANTED; do
|
||||
if ! cmp -s "$SRC/$f" "$TARGET/$f"; then
|
||||
cp "$SRC/$f" "$TARGET/$f.tmp"
|
||||
mv "$TARGET/$f.tmp" "$TARGET/$f"
|
||||
log "updated $f"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
|
||||
# Remove deployed .js files that are no longer listed.
|
||||
for path in "$TARGET"/*.js; do
|
||||
[ -e "$path" ] || continue
|
||||
name=$(basename "$path")
|
||||
if ! printf '%s\n' $WANTED | grep -qx "$name"; then
|
||||
rm -f "$path"
|
||||
log "removed $name (not in DEPLOY)"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
|
||||
log "synced $(git rev-parse --short HEAD) (changed=$changed)"
|
||||
Loading…
Reference in a new issue