The website for the Purpose Investor Network (PIN) — a philanthropic impact investing organization mentoring the next generation of impact investors. Static front-end, serverless Express backend, content managed through an admin dashboard.
- Public site (
front-end/) — Homepage, About, Roundtables & Initiatives, Education, Investments (portfolio/showcase), Events, Press/Updates, and a member questionnaire/survey. - Admin dashboard (
private/backend/admin/) — staff-only content management for every content type above, plus user invites. - Member area (
private/backend/member/) — a coming-soon page today; seedocs/DATA-MODEL.md's Role & Permission Model for what's still to come.
See docs/ARCHITECTURE.md for the full AWS stack, deploy flow, and staging setup. See
docs/DATA-MODEL.md for the DynamoDB schema and API reference. See .claude/CLAUDE.md
for coding standards and conventions this codebase follows.
- Frontend: static HTML/CSS/JS, no framework, shared component library in
shared/ - Backend: Express (
private/backend/), wrapped for Lambda viaserverless-http - Database: DynamoDB — one table per content type
- Auth:
express-session+ a Users table (no third-party provider) - Hosting: S3 + CloudFront (frontend), API Gateway + Lambda (backend)
- Monitoring: Sentry, Plausible, PostHog
Full details in docs/ARCHITECTURE.md.
Requirements: Node.js 18+, npm.
npm install
npm startServes the site at http://localhost:3000. Copy .env.example to .env and fill in
values first. Without the AWS_<THING>_TABLE vars (see docs/DATA-MODEL.md) pointing at
real DynamoDB tables, content routes (Roundtables, Posts, etc.) return empty rather than
erroring, so the site will run but show no content locally unless those are set.
npm install also enables this repo's git hooks (.githooks/, via a prepare script
running git config core.hooksPath .githooks): pre-commit rejects a commit made
directly on main or stage, pre-push rejects a push whose target is main or
stage. Both only change through a GitHub PR. For a deliberate exception, set
ALLOW_PROTECTED_BRANCH=1 yourself.
The one exception is survey responses: they always write to a local JSON file
(private/backend/survey.db in dev, /tmp in Lambda) in parallel with DynamoDB, so
that flow works out of the box.
npm testRuns the Jest/Supertest suite — see .claude/CLAUDE.md's Testing section for what to
prioritize when adding to it.
Push-triggered GitHub Actions handle deploys — main → production,
stage → staging. See docs/ARCHITECTURE.md for exactly what each deploy does
(frontend sync + CloudFront invalidation, Lambda packaging, env var reconciliation, IAM
policy setup).
- All changes on feature/working branches — no direct commits to
stageormain - Manual merge only: feature branch →
stage(test) →main(production) - No automated or agent-initiated merges into either branch
Full conventions (branch naming, commit style, pre-merge checklist) in
.claude/CLAUDE.md.
Built: public site content areas (Homepage, About, Roundtables/Initiatives,
Education, Investments, Events, Press/Updates), admin dashboard CRUD for all of the
above, production + staging environments, Sentry/Plausible/PostHog integration.
Admin/Chair/Member role differentiation: role-aware invites (with resend and an
Admin-triggered password reset), Chair-scoped Member management, Chair write and read
access to their own Roundtable's Initiatives/Posts/Investments, Chair access to their
own Roundtable's own fields and the shared image library, and role-aware gating on
every content route (Roundtables, Press, Events, Images included) and the admin
dashboard page itself, including the dashboard UI itself: a Chair sees only the tabs
and controls they can use (no Press, no Events, no unfiltered Roundtable/Initiative/
Investment forms), so a Chair session no longer hits a failed-to-load toast for
something the backend was always going to reject. A My Profile section in the admin
dashboard's Settings tab lets an Admin or Chair edit their own name/phone/address,
validated server-side. A /member coming-soon page, with
each role landing on its own home after login (Member → /member, Chair and Admin →
/admin). Campaigns: durable results storage for the survey, newsletter, and
membership waitlist, plus an always-on interest form on every Roundtable and Initiative
page, with per-campaign results emails and copy-paste email lists in the admin
dashboard. See docs/DATA-MODEL.md's Role & Permission Model section for exactly how
each role is scoped, including a per-role table of the admin dashboard's own tab and
control visibility.
In progress: the Member area is a coming-soon shell today — profile editing,
education content, an events calendar, and a member news feed are still to come.
Role-aware memberOnly content visibility is also still open. See docs/DATA-MODEL.md's
"Not yet implemented" list at the end of the Role & Permission Model section for the
full detail.
Known gaps: bulk email sending isn't built — Campaigns gives a copy-paste email list per campaign instead of sending from the dashboard directly.