The service account is silently filtered out of Object Type 109 (Store
Address / Hierarchy) despite having schema-level read on schema 68, so
every AQL against the store type returns total=0. Until that permission
is granted, resolve store numbers from a local cache populated by a
personal PAT (different auth path, different account, has the role).
- new: src/services/jira/assetsSyncClient.js — Basic-auth axios against
api.atlassian.com/jsm/assets/workspace/{ws}/v1, credentials sourced
from ASSETS_SYNC_EMAIL / ASSETS_SYNC_TOKEN (loaded from Keychain by
bin/load-assets-sync-secret.sh so the PAT never touches .env)
- new: src/services/jira/storesCache.js — in-memory Map + on-disk JSON
at data/stores.json (gitignored), atomic write, paginated full sync
via AQL (objectTypeId=N), boot-time load + background refresh if
stale, periodic setInterval every STORES_CACHE_REFRESH_HOURS
- new: bin/load-assets-sync-secret.sh — Keychain -> env var wrapper
(security find-generic-password -s jira-assets-sync -a <email>)
- change: resolveStoreAssetReference now tries cache -> live PAT -> the
existing service-account AQL, in that order; the fallback path is
preserved so this cleanly deactivates once the permission on #1 is
fixed. Error message names all three routes and points at the refresh
endpoint.
- new admin routes: GET /api/wxccai/admin/storesCache/status,
POST /api/wxccai/admin/storesCache/refresh
- app.js kicks off storesCache.init() after listen()
- config: STORES_CACHE_ENABLED / _PATH / _REFRESH_HOURS /
_STALE_AFTER_HOURS / _PAGE_SIZE / _MAX_PAGES, plus intFromEnv /
boolFromEnv helpers
- .gitignore adds data/; .env.example documents the new vars; README
adds an "Admin" endpoints section and a "Stores cache" setup guide
Co-authored-by: Cursor <cursoragent@cursor.com>
75 lines
3 KiB
Bash
Executable file
75 lines
3 KiB
Bash
Executable file
#!/usr/bin/env sh
|
|
# -----------------------------------------------------------------------------
|
|
# load-assets-sync-secret.sh
|
|
#
|
|
# Loads the personal Atlassian PAT used for the Assets → Stores cache sync
|
|
# from macOS Keychain into ASSETS_SYNC_TOKEN, then execs whatever command you
|
|
# passed as arguments.
|
|
#
|
|
# Rationale:
|
|
# The service account is silently filtered out of Object Type 109 (Store
|
|
# Address / Hierarchy) — see Forgejo issue #1. The stores cache is populated
|
|
# using a personal PAT instead. This PAT authenticates as a real user and so
|
|
# should NEVER sit in .env in cleartext; the Keychain is a safer store.
|
|
#
|
|
# Setup (one-time, per machine):
|
|
#
|
|
# # 1. Store the token in Keychain
|
|
# security add-generic-password \
|
|
# -s jira-assets-sync \
|
|
# -a mcqueenj@ae.com \
|
|
# -w '<paste-your-atlassian-api-token-here>' \
|
|
# -U
|
|
#
|
|
# # 2. Set the email (either exported here or in ~/.zshrc)
|
|
# export ASSETS_SYNC_EMAIL="mcqueenj@ae.com"
|
|
#
|
|
# Usage:
|
|
#
|
|
# ./bin/load-assets-sync-secret.sh npm start
|
|
# ./bin/load-assets-sync-secret.sh node src/app.js
|
|
#
|
|
# Environment variables (override defaults if needed):
|
|
#
|
|
# ASSETS_SYNC_KEYCHAIN_SERVICE Keychain service name (default: jira-assets-sync)
|
|
# ASSETS_SYNC_KEYCHAIN_ACCOUNT Keychain account name (default: value of $ASSETS_SYNC_EMAIL)
|
|
# ASSETS_SYNC_TOKEN If already set, skip the Keychain read entirely.
|
|
# -----------------------------------------------------------------------------
|
|
|
|
set -eu
|
|
|
|
if [ -z "${ASSETS_SYNC_TOKEN:-}" ]; then
|
|
SERVICE="${ASSETS_SYNC_KEYCHAIN_SERVICE:-jira-assets-sync}"
|
|
ACCOUNT="${ASSETS_SYNC_KEYCHAIN_ACCOUNT:-${ASSETS_SYNC_EMAIL:-}}"
|
|
|
|
if [ -z "$ACCOUNT" ]; then
|
|
printf 'load-assets-sync-secret.sh: neither ASSETS_SYNC_TOKEN nor an account name is set.\n' >&2
|
|
printf ' Export ASSETS_SYNC_EMAIL=you@ae.com or ASSETS_SYNC_KEYCHAIN_ACCOUNT=<account>.\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v security >/dev/null 2>&1; then
|
|
printf 'load-assets-sync-secret.sh: `security` not found (this script is macOS-only).\n' >&2
|
|
printf ' On Linux/prod, export ASSETS_SYNC_TOKEN directly from your secret manager.\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! ASSETS_SYNC_TOKEN=$(security find-generic-password -s "$SERVICE" -a "$ACCOUNT" -w 2>/dev/null); then
|
|
printf 'load-assets-sync-secret.sh: Keychain lookup failed for service="%s" account="%s".\n' "$SERVICE" "$ACCOUNT" >&2
|
|
printf ' Store the token with:\n' >&2
|
|
printf ' security add-generic-password -s %s -a %s -w '\''<token>'\'' -U\n' "$SERVICE" "$ACCOUNT" >&2
|
|
exit 1
|
|
fi
|
|
export ASSETS_SYNC_TOKEN
|
|
fi
|
|
|
|
# Only echoes existence, never the token itself.
|
|
printf 'load-assets-sync-secret.sh: ASSETS_SYNC_TOKEN loaded (%d chars) for %s\n' \
|
|
"${#ASSETS_SYNC_TOKEN}" "${ASSETS_SYNC_EMAIL:-<unset ASSETS_SYNC_EMAIL>}" >&2
|
|
|
|
if [ $# -eq 0 ]; then
|
|
printf 'load-assets-sync-secret.sh: no command given; exiting after loading token.\n' >&2
|
|
exit 0
|
|
fi
|
|
|
|
exec "$@"
|