148 Commits

Author SHA1 Message Date
96ff2a85d6 Expose ShiftKind in admin panel with Dropdown.Button picker
Shift.kind existed in the schema and on the volunteer dashboard's
training filter but there was no way to create or edit a shift's
kind from the admin UI — every shift landed as the default CANVASS.
This wires the full loop:

Backend: createShiftSchema / updateShiftSchema / listShiftsSchema
and their series counterparts now accept a kind field. The shifts
service passes it through on create and filters by it on list.
Series shift templates propagate kind to every generated shift
instance so a training series produces training shifts.

Admin UI: the Create Shift button becomes a Dropdown.Button. The
main action creates a Canvass shift (default); the menu offers
Training, Event Staffing, Phone Bank, and Other. Each menu item
pre-fills the form's kind field. A kind Select appears at the top
of the form so admins can change it mid-creation or on edit. The
shifts table gets a color-coded Kind column and the toolbar gets
a kind filter.

Bunker Admin
2026-04-11 11:09:23 -06:00
76fd3c7065 Fix action-campaigns list shape + widen volunteer dashboard on desktop
The admin list endpoint returned a bare array but the frontend list
page expected { items: [] }. Newly-created campaigns were saving
successfully but rendering as an empty list. Wrap the response to
match the contract that the frontend was already coded against.

VolunteerLayout capped content at 800px wide, which suits the
phone-first volunteer tool pages but left most of the desktop screen
empty on the new dashboard. Bump the cap to 1280px for /volunteer
only so other volunteer pages keep their narrow sizing.

Bunker Admin
2026-04-11 10:37:40 -06:00
c00b4432d7 Add volunteer dashboard page + ActionCampaigns admin editor
VolunteerDashboardPage replaces the old direct-to-map landing at
/volunteer with a personalized action hub modeled on the For Alberta
For Canada layout: profile + referral on top, action campaign goal
tile next to a featured event CTA, training shifts + my events +
points + resources. The map moves to /volunteer/map as a fullscreen
route outside VolunteerLayout. CutRedirect updated to match.

VolunteerFooterNav and VolunteerLayout drawer get Home/Map split
tabs. AppLayout sidebar gets an Action Campaigns link under the
Advocacy menu.

ActionCampaignsPage lists campaigns; ActionCampaignEditorPage edits
metadata + steps with type-aware target pickers per ActionStepKind
(video picker, petition picker, ticketed-event picker, etc).
CUSTOM/VISIT_LINK steps get a free-form target URL. Reorder via
up/down buttons.

Bunker Admin
2026-04-11 10:21:10 -06:00
ae5a90d8d4 Add Document model upload + download routes (PDFs as first-class media)
Documents are a separate media type from Video/Photo because the
Photo pipeline assumes raster images (sharp metadata, EXIF, variant
generation). The new routes mirror the photo upload pattern but
target the Document Prisma model and serve files with
Content-Disposition: attachment so browsers download instead of
inline-rendering. Tag-based categorization (e.g. 'volunteer-resource')
lets the volunteer dashboard surface curated downloads alongside
videos and photos.

Admin Library page gets a Documents tab for upload/list/edit/delete
with the same affordances as the existing photo and video tabs.

Bunker Admin
2026-04-11 10:20:54 -06:00
ed011a762b Add action-campaigns module + volunteer-dashboard aggregator
ActionCampaign service exposes admin CRUD plus a per-user composer
(getActiveForUser) that fans out a per-step completion check against
the existing per-user model for each ActionStepKind: VideoView for
WATCH_VIDEO, CampaignEmail for SUBMIT_INFLUENCE, PetitionSignature
for SIGN_PETITION (matched by signer email), Ticket for RSVP_EVENT,
ShiftSignup for SIGNUP_SHIFT, ChallengeTeamMember for JOIN_CHALLENGE.
CUSTOM and VISIT_LINK steps complete only via explicit self-report.
An existing ActionStepCompletion row also short-circuits the check
so manual marking and idempotency both work.

Volunteer dashboard aggregator at GET /api/volunteer/dashboard
composes the active campaign with the user's profile, referral info,
upcoming featured event, training shifts (Shift.kind='TRAINING'),
ticketed events the user holds, an engagement-counter point total
(placeholder until the Redis engagement score is wired in), and
resources tagged 'volunteer-resource' across Document/Video/Photo.

Bunker Admin
2026-04-11 10:20:26 -06:00
3fc67cd81a Add ActionCampaign + Document models for volunteer dashboard
Foundation schema for the FAFC-style volunteer dashboard. Adds:
- ActionCampaign / ActionStep / ActionStepCompletion (stacked-action
  mini-campaigns where steps reference existing entities like videos,
  petitions, ticketed events; completion is detected at query time
  against the per-user model for each step kind)
- Document model for downloadable resources (PDFs etc.) — Photo's
  EXIF/sharp pipeline can't host non-image files
- Shift.kind discriminator (ShiftKind enum) so training shifts can
  surface separately on the dashboard
- TicketedEvent.featured for the "Take Action" CTA tile

Bunker Admin
2026-04-10 21:26:56 -06:00
5f0ae6bc5a Revert NocoDB auto sign-in, keep CSP fix for embed proxy
NocoDB v2 stores auth tokens in-memory (Pinia store), not in cookies
accessible to external pages. The auth bridge approach can't inject
tokens into NocoDB's SPA state. Reverted to the original banner
approach ("sign in to NocoDB in a new tab").

Kept: CSP fix (frame-ancestors http://localhost:* instead of just
localhost, which only matched port 80).

Bunker Admin
v2.8.2
2026-04-09 14:01:02 -06:00
aa69048024 Fix Gitea init: must-change-password flag syntax + auth check
- Use --must-change-password=false (equals syntax) in gitea CLI
  The space-separated form was parsed as boolean flag + extra arg
- Fix auto-setup readiness check: use Basic Auth instead of
  unauthenticated /version endpoint (blocked by REQUIRE_SIGNIN_VIEW)
- Increase retries to 8 to accommodate gitea-init container startup

Bunker Admin
2026-04-09 13:32:44 -06:00
c180bb5ace Rework Gitea init to use separate init container pattern
The previous approach (custom CMD on gitea-app) failed because:
- Gitea's entrypoint generates app.ini as root, then drops to git user
- Overriding CMD ran our script before app.ini was generated
- su-exec to git user lost access to the entrypoint-generated config

Now uses the same pattern as nocodb-init: a separate container that
depends on gitea-app being healthy, shares the gitea-data volume
(which has app.ini), and runs gitea admin user create.

Bunker Admin
2026-04-09 13:25:56 -06:00
c5209887cc Fix gitea-init.sh running as root — drop to git user via su-exec
The Gitea Docker entrypoint sets up directories as root then exec's
the CMD still as root. Gitea refuses to run as root, so our init
script must re-exec itself as the 'git' user via su-exec before
running any gitea commands.

Bunker Admin
2026-04-09 13:14:48 -06:00
ca446136a1 Fix set -e crash in pangolin_create_resources arithmetic
((created++)) returns exit code 1 when created=0 (post-increment
evaluates to 0, which is falsy), killing the script under set -e.
Use x=$((x + 1)) instead.

Bunker Admin
2026-04-09 13:03:12 -06:00
0510420772 Fix pangolin_create_site blocking on read in non-interactive mode
The site name prompt used read -rp which blocks when stdin is piped.
Now uses default name automatically when NON_INTERACTIVE=true.

Bunker Admin
2026-04-09 12:57:54 -06:00
36b709b911 Automate Gitea init, NocoDB auto sign-in, and fix prod compose
- Add scripts/gitea-init.sh: runs migrations + creates admin user on
  first boot, replacing the manual installation wizard
- Set GITEA__security__INSTALL_LOCK=true in both compose files
- Add NocoDB auth bridge (nginx) + /api/services/nocodb-auth proxy
  endpoint so the admin iframe auto-authenticates
- Update NocoDBPage.tsx to fetch token and use auth bridge flow
- Fix docker-compose.prod.yml missing Gitea env vars for API container
  (GITEA_URL, GITEA_API_TOKEN, GITEA_ADMIN_PASSWORD, etc.)
- Pass NC_ADMIN_EMAIL/PASSWORD to API for NocoDB auth proxy
- Increase Gitea auto-setup retries from 3 to 6 with admin auth check
- Update config.sh non-interactive mode to set GITEA_ADMIN_USER
- Include gitea-init.sh in release tarball (build-release.sh)

Bunker Admin
2026-04-09 12:49:33 -06:00
0a8e1fe46b Remove deployment report from repo (moved to gitignored docs/)
Bunker Admin
2026-04-09 12:08:15 -06:00
f8c8a939d7 Add full non-interactive mode to config.sh
New CLI flags for scripted deployments:
  --smtp-host/port/user/pass   Production SMTP configuration
  --pangolin-api-url/key/org-id/endpoint/site  Full Pangolin tunnel setup
  --mapbox-key                 Mapbox API key
  --maxmind-account-id/license-key  MaxMind GeoIP credentials

With --pangolin-site=new, config.sh creates a Pangolin site, fetches
Newt credentials, and creates all resources+targets automatically.
With --pangolin-site=existing, it connects to the first available site.

Bunker Admin
2026-04-09 12:08:05 -06:00
bca4cb8227 Fix Pangolin site creation: omit address field from payload
The clientAddress from pickSiteDefaults lacks CIDR notation and gets
rejected by the Pangolin API. Omitting it lets Pangolin auto-assign.

Bunker Admin
2026-04-09 11:58:14 -06:00
f0d994074d Update admin modals and page components for mobile responsiveness
Bunker Admin
v2.8.1
2026-04-09 11:43:23 -06:00
849dea7ce2 Fix config.sh Pangolin setup and MongoDB init for fresh deployments
- Fix Pangolin endpoint: ask separately from API URL (different hostnames)
- Add pangolin_create_resources() to create resources + targets during setup
- Set all resources as public (no SSO/blockAccess) automatically
- Fix API health check URL to use actual org endpoint
- Fix MongoDB entrypoint: delegate to docker-entrypoint.sh so INITDB user
  creation works on fresh volumes (was bypassing Docker's init sequence)

Bunker Admin
2026-04-09 11:43:13 -06:00
72dbd0189c Pass GITEA_SSO_SECRET and SERVICE_PASSWORD_SALT to API container
These env vars were defined in .env but never mapped into the API
container's environment block, causing silent fallback to
JWT_ACCESS_SECRET and security warnings on startup.

Bunker Admin
2026-04-09 09:26:51 -06:00
0b0c33cfee Add ccp-agent to build pipeline and fix registry image name
- Added ccp-agent as 5th service in build-and-push.sh (builds from
  changemaker-control-panel/agent/Dockerfile)
- Fixed prod compose image name to match registry convention:
  changemaker-ccp-agent (consistent with changemaker-api, etc.)

Bunker Admin
2026-04-08 16:12:53 -06:00
c6f8a49925 Add CCP registration page to CML admin panel
Operators can now register with a Control Panel directly from the admin
GUI (Services → Control Panel) without SSH access. Uses the existing
updateEnvFile + dockerService pattern from the Pangolin setup.

New endpoint: /api/ccp-registration (status, register, unregister)
New page: ControlPanelPage with form for CCP URL, invite code, agent URL
Also passes CCP env vars through docker-compose to the API container.

Bunker Admin
2026-04-08 15:13:28 -06:00
215da79284 Add register-with-ccp.sh for existing installations
Interactive script that configures .env, adds ccp-agent to COMPOSE_PROFILES,
and starts the agent container. Supports --ccp-url, --invite-code, --agent-url
flags for non-interactive use, and --unregister to remove registration.

Bunker Admin
2026-04-08 15:04:38 -06:00
145ba4268f Update DEV_WORKFLOW.md with Gitea token docs and release tag checking
Bunker Admin
v2.8.0
2026-04-07 17:26:02 -06:00
d010993994 Add pagination to public endpoints, Pangolin site picker, and docs editor toolbar
- Paginate public APIs: campaigns, petitions, shifts, products, pages, shop
- Add safety caps (take limits) to gallery ads, cuts, plans, donation pages
- Add Pangolin connect-site endpoint with .env writer and site ID validation
- Add formatting toolbar + keyboard shortcuts to shared doc editor
- Fix Dockerfile to support su-exec privilege dropping for mounted volumes
- Fix duplicate WebSocket headers in nginx API location block
- Update MkDocs site build and social card assets

Bunker Admin
v2.7.3
2026-04-07 16:50:20 -06:00
513b8cfea5 Add openssl to CCP API container for certificate generation
The certificate service uses openssl CLI to generate CA and agent certs.
Alpine base image doesn't include it by default.

Bunker Admin
2026-04-07 15:41:17 -06:00
38ccaa8a5b Add remote instance management with mTLS agent and phone-home registration
Enables the CCP to manage CML instances on remote servers via a lightweight
HTTP agent. Key components:

- ExecutionDriver abstraction (local-driver.ts / remote-driver.ts) routes
  operations to local Docker or remote agent transparently
- Remote agent package (agent/) with mTLS authentication, Docker Compose
  operations, file management, backup/upgrade delegation
- Certificate service using openssl CLI for CA management and cert issuance
- Phone-home registration: remote agents register via invite code, CCP admin
  approves, agent receives mTLS cert bundle automatically
- config.sh integration with configure_control_panel() section
- ccp-agent Docker Compose service (profile-gated)
- Frontend: AgentRegistrationsPage, InviteCodesPage, Remote Agents sidebar menu
- Security hardened: cert bundle wiped after delivery, shell injection prevention
  via execFile, command allowlist with metachar rejection, rate-limited public
  endpoints, auto-populated fingerprint pinning

Also wires ENABLE_SOCIAL/PEOPLE/ANALYTICS through env.ts, seed.ts, and
docker-compose env passthrough (from previous session).

Bunker Admin
2026-04-07 15:24:33 -06:00
d17e197a1b Fix Vite allowedHosts blocking production domains
Set allowedHosts to true since nginx handles Host header
validation. The previous `.${domain}` pattern used the
build-time DOMAIN value, which breaks when the same image
is deployed to a different domain.

Bunker Admin
2026-04-07 14:21:16 -06:00
cbfa4f9e28 Add uninstall.sh and test-deployment.sh to release tarball
Bunker Admin
2026-04-07 14:14:45 -06:00
530551f568 Fix deployment issues found during end-to-end testing
- install.sh: Use tar --strip-components=1 instead of mv for robust
  extraction when install dir partially exists (root-owned Docker
  artifacts)
- config.sh: Add --non-interactive mode (--domain, --admin-password,
  --enable-all flags) for CI/CD and automated deployments
- docker-entrypoint.sh: Validate critical env vars on startup, fail
  early with clear messages instead of silent failures
- docker-compose.yml: Change Redis eviction policy from allkeys-lru
  to noeviction (required by BullMQ job queues)
- Prisma: Add missing petitions.coverVideoId migration (schema had
  the column but migration omitted it, causing 500 on public endpoint)
- Add scripts/uninstall.sh for clean removal including root-owned files
- Add scripts/test-deployment.sh for automated post-install verification

Bunker Admin
2026-04-07 14:06:05 -06:00
74e5fa6475 Clean up obsolete files and refresh MkDocs site
Remove unused planning docs (FEDERATION_PLAN.md, SERVICE_INTEGRATIONS.md),
temporary screenshots, update .gitignore for Playwright MCP logs, and
refresh MkDocs site build with updated repo data.

Bunker Admin
v2.7.0 v2.7.1
2026-04-03 08:52:15 -06:00
72622671a2 Add petition/action pages with signature collection, CRM integration, and campaign linking
New influence submodule for public petitions with configurable sign forms,
email verification, GeoIP tracking, dedup, CSV export, admin moderation,
and post-sign CTA linking to advocacy campaigns. Includes competitive
analysis document covering 30+ campaign tech platforms.

Bunker Admin
2026-04-03 08:49:49 -06:00
08bd1f92b0 Add unified analytics system with GeoIP geo-tracking
Full analytics platform with MaxMind GeoLite2 IP-to-location resolution,
cross-module dashboard (docs, video, photo), user drill-down, volunteer
self-service stats, and ANALYTICS_ADMIN role with feature flag controls.

- ANALYTICS_ADMIN role + ANALYTICS_ROLES group across backend and frontend
- GeoIP service (MaxMind GeoLite2, lazy-loaded, graceful degradation)
- Geo fields (country, region, city, lat/lng) on DocsPageView, VideoView, PhotoView
- IP resolved to geo before SHA-256 hashing (privacy-preserving)
- Unified analytics module: overview, geo, content, user engagement endpoints
- 4 admin dashboard pages: Overview, Geography (Leaflet map), Content, Users
- Volunteer MyAnalyticsPage for self-service activity stats
- Settings UI: enableAnalytics, analyticsGeoEnabled, trackAuthenticatedUsers, retentionDays
- Scheduled cleanup job respecting configurable retention period
- config.sh: Analytics + MaxMind prompt in configure_features()
- Control panel: enableAnalytics flag, template, discovery, wizard, detail page
- Docker: geoip volume mount, MaxMind env vars, entrypoint auto-download
- Nginx: X-Forwarded-For fix ($proxy_add_x_forwarded_for) for real client IP
- Express trust proxy set to 2 for Pangolin/Newt tunnel chain
- CORS updated for docs origin (cmlite.org + docs.cmlite.org)
- Lander page: added docs-analytics tracking snippet
- Prisma migration: 20260402100000_add_analytics_system

Bunker Admin
2026-04-03 08:47:44 -06:00
0a20444a74 Archive addition 2026-04-02 15:14:27 -06:00
610f547dbf Fix dashboard mobile layout: header overflow, welcome banner, and stats grid
Hide nav icon bar and volunteer button on mobile (accessible via drawer),
stack welcome banner vertically with proper nowrap, replace status bar
flex row with 3-column CSS grid using MobileQuickStat component.

Bunker Admin
2026-04-02 15:12:27 -06:00
6db44eadc6 Fix mobile layout shift from typewriter text wrapping in hero section
Bunker Admin
2026-04-02 15:12:25 -06:00
5a0c4641a1 Security audit fixes, mobile responsiveness across 40+ admin pages
Security hardening from Mar 31 audit:
- Separate login rate limit (10/15min) from general auth budget (15/15min)
- Timing-safe webhook secret comparison (Listmonk)
- Docs file creation ACL check (matches PUT/DELETE guards)
- Key separation warnings for GITEA_SSO_SECRET and SERVICE_PASSWORD_SALT
- Clear GITEA_ADMIN_PASSWORD from .env after auto-setup
- SQL injection prevention in effectiveness groupBy (pre-validated map)
- Token hashing for password reset and verification tokens

Mobile responsiveness (Phase 2C):
- Add MobilePageHeader component and useMobile hook
- Responsive table columns (hide secondary cols on mobile)
- scroll={{ x: 'max-content' }} across all data tables
- Mobile-adapted layouts for Dashboard, Settings, Calendar, SMS, Social pages
- Conditional toolbar buttons on mobile viewports

Infrastructure:
- Updated docker-compose and nginx templates
- Build script and mirror script updates

Bunker Admin
2026-03-31 18:30:17 -06:00
d7ab8f0d99 Add file move capabilities to docs editor file tree
Drag-and-drop tree nodes to move files/folders between directories (desktop),
and right-click "Move to..." with searchable directory picker modal (desktop + mobile).

Bunker Admin
2026-03-31 13:44:03 -06:00
c306e061ab Generate GITEA_SSO_SECRET and SERVICE_PASSWORD_SALT in config wizard
New installs now get dedicated secrets for Gitea SSO cookie signing and
service password derivation, rather than falling back to JWT_ACCESS_SECRET.
Existing installs are unaffected (update_env_var_if_empty preserves values).

Bunker Admin
2026-03-31 12:13:32 -06:00
f378db89b5 Separate local vs remote Gitea API tokens to prevent credential collision
GITEA_API_TOKEN is for the local platform Gitea (docs comments, user
provisioning, SSO). New GITEA_REGISTRY_API_TOKEN is for the remote
registry at gitea.bnkops.com (release uploads via build-release.sh).

Previously both contexts shared one variable, causing auth failures
when the token for one instance was used against the other.

Bunker Admin
2026-03-31 11:53:20 -06:00
91db29402c Add Gitea SSO, fix security audit findings, harden production defaults
Gitea SSO: cookie-based single sign-on via nginx auth_request — sets
cml_session cookie on login/refresh, validates via /api/auth/gitea-sso-validate,
injects X-WEBAUTH-USER header for reverse proxy auth. Dedicated GITEA_SSO_SECRET
and SERVICE_PASSWORD_SALT env vars isolate secret rotation.

Security fixes from March 30 audit: IDOR on ticketed events (requireEventOwnership
middleware), IDOR on action items (admin/assignee/creator check), path traversal
on photos (resolve-based validation), CSV upload size limit (5MB), shared calendar
email exposure removed.

Gitea provisioner: auto-sync docs repo collaborator access based on role
(CONTENT_ROLES get write, SUPER_ADMIN gets admin). Gitea client extended
with collaborator management API methods.

Production hardening: NODE_ENV defaults to production in docker-compose.prod.yml,
Grafana anonymous auth disabled, install.sh branch ref updated to main.

Admin UI: moved docs reset from toolbar to MkDocs Settings danger zone,
improved collab Ctrl+S to explicitly save + cache-bust preview.

MkDocs site rebuild with updated repo data, upgrade screenshots, and content.

Bunker Admin
v2.4.0
2026-03-31 11:20:01 -06:00
9321aeb263 Move SMS phone bridge from campaign_connector submodule into main repo
Consolidates the Termux SMS server code (previously in a separate
campaign_connector git submodule) into termux-sms/ at repo root.
Updates phone clone commands to use sparse checkout so only the
termux-sms/ directory is downloaded onto the Android device.

Bunker Admin
2026-03-31 11:04:14 -06:00
5d15b4cffa Add engagement scoring and homepage stats EventBus listeners
- Engagement scoring listener: 11 event subscriptions, weighted scoring
  (donation=50, subscription=40, shift=20, canvass=15, email=10, video=3),
  Redis sorted set leaderboard, per-contact score + last-activity tracking
- Homepage stats listener: 12 subscriptions, incremental Redis counters
  (emails, signups, donations, responses, canvass, videos), capped recent
  activity lists (last 20 per type), cache invalidation on data changes
- GET /api/homepage/live-stats — public real-time counters + recent activity
- GET /api/observability/engagement-leaderboard — admin top-N contacts
- Total: 8 listeners, 70 subscriptions across all modules

Bunker Admin
2026-03-31 10:21:05 -06:00
902adce646 Add Straw Polls feature: quick opinion polling with public landers, MkDocs widgets, and social integration
Full-stack implementation across 7 sprints:
- Backend: 5 Prisma models (StrawPoll, Option, Vote, Comment, Challenge), 4 enums, POLLS_ADMIN role,
  admin CRUD routes, public voting/SSE/widget endpoints, BullMQ auto-close queue, rate limiting
- Admin: StrawPollsPage with inline drawers (campaigns pattern), PollResults bar chart, sidebar under Advocacy
- Public: dedicated poll lander with real-time SSE updates, browse page, anonymous voting with token dedup
- MkDocs: straw-poll-widget.js hydration (inline vote + card link modes), GrapesJS block types
- Social: feed activity (poll_voted), friend badge integration, challenge notifications, notification preferences
- Feature flag: enablePolls toggle in Settings, FeatureGate, Zod schema

Bunker Admin
2026-03-31 10:16:56 -06:00
68434c51a6 Extend EventBus: RC notifications, CRM activity, Gancio migration, calendar source types
- Add 7 new RC notification types: campaign published, donations, subscriptions,
  SMS escalations, user approved, video published, ticketed events
- Add CRM activity entries for subscription activated and email bounced
- Migrate ticketed-events Gancio sync from inline calls to EventBus listener
- Add meeting.created/deleted events from jitsi.routes.ts
- Add SHIFT, MEETING, TICKETED_EVENT to CalendarItemSource enum (Prisma migration)
- Update calendar-sync listener to use proper source types instead of MANUAL
- Total: 45 listener subscriptions across 6 modules, zero inline sync calls remaining

Bunker Admin
2026-03-31 10:04:44 -06:00
075a7c8c4a Redesign hero section: two-column layout, showcase cards, animations
- Two-column desktop layout (left: text/CTAs, right: feature showcase)
- Typewriter rotating words animation cycling through 8 platform capabilities
- Feature showcase with 4 auto-rotating screenshot cards (campaigns, canvassing, media, shifts)
- Staggered feature pill badges linking to corresponding sections below
- Terminal quick-deploy snippet with copy-to-clipboard
- Canvas particle drift background animation
- Count-up stats with IntersectionObserver
- Real screenshots replace mock data in showcase cards
- Light/dark theme support for all new elements
- Mobile responsive: single-column stack, overflow containment, scaled typography
- prefers-reduced-motion respected across all animations

Bunker Admin
2026-03-31 10:01:48 -06:00
0c2ffe754e Harden Stripe payment integration: 15 security fixes from audit
Addresses 11 original findings (1 critical, 3 high, 4 medium, 3 low)
plus 4 additional findings from security review:

- Mask secrets in PUT /settings response (was leaking decrypted keys)
- Add paymentCheckoutRateLimit (10/hr/IP) to all 5 checkout endpoints
- Implement durable audit logging to payment_audit_log table
- Pin Stripe API version to 2026-01-28.clover (SDK v20.3.1)
- Add charge.dispute.created/closed webhook handlers with DISPUTED status
- Restore tickets on dispute won, handle charge_refunded closure
- Guard against sentinel passthrough corrupting stored Stripe keys
- Wrap refund DB updates in try/catch with webhook reconciliation fallback
- Add $transaction for product maxPurchases race condition
- Remove dead Payment model lookup from handleChargeRefunded
- Cap donation amount at $100k in both schemas
- Add requirePaymentsEnabled middleware on all checkout routes
- Remove Stripe internal IDs from CSV exports
- Add Cache-Control: no-store on admin settings responses

Bunker Admin
2026-03-31 08:34:23 -06:00
3de1d3fca5 Rewrite README as visual explainer with screenshots and docs link
Bunker Admin
v2.5.0 v2.6.0
2026-03-30 11:44:25 -06:00
a436c494fd Add duplication guard in collab onChange to detect and auto-fix doubled content
Y.js CRDT merges can duplicate content when a client reconnects after
external file modifications (e.g., API PUT while collab is active).
The guard detects when content is exactly doubled and auto-trims it.

Bunker Admin
2026-03-27 13:46:35 -06:00
078bb6e313 Fix collab preview refresh: use src reassignment with cache-buster instead of contentWindow.reload
Cross-origin iframes may silently fail on contentWindow.location.reload().
Use src reassignment with cache-buster query param to force fresh load.
Increased debounce to 2.5s to give MkDocs more time to rebuild.

Bunker Admin
2026-03-27 13:41:02 -06:00
eb16815f91 Fix blog hooks: unwrap API response envelope for authors and categories
The API returns { authors: {...} } and { categories: [...] } but hooks
were expecting the unwrapped values directly. Also add defensive guards
in components for undefined props during initial render.

Bunker Admin
2026-03-27 13:33:47 -06:00