Profile
Back to NewsBack
GitHub Trending 10 min
Reader Mode
socialincome-san/public: Fighting global poverty with the help of everyday people and your coding skills. Public repository of the NGO and global initiative Social Income.

socialincome-san/public: Fighting global poverty with the help of everyday people and your coding skills. Public repository of the NGO and global initiative Social Income.

12 hours ago

Social Income

#Tech4Good   #OpenSource   #Solidarity

!Social Income Logo

Social Income is a radically simple solution in the fight against poverty. The open-source initiative converts donations into an unconditional basic income, sent directly to the mobile phones of people living in poverty in the Global South.

https://user-images.githubusercontent.com/6095849/191377786-10cdb4a1-5b25-4512-ade9-2cc0e153d947.mp4

What Is In This Repository?

This repository contains the public website, internal tools, local development seed data, infrastructure code, and the recipient mobile app.

/
├─ recipients_app/        Mobile app for Social Income recipients
├─ seed/                  Firebase emulator seed data
└─ website/               Next.js app, APIs, database, infra, and tests

website/

The main Next.js application. It contains:

  • Public website: the public Social Income website. Parts are still hardcoded,
while more content is being moved to Storyblok CMS.
  • Portal: internal operations tool for program management, payments,
recipients, contributors, and admin functionality.
  • Dashboard: contributor self-service area for payments, subscriptions, and
personal details.
  • Partner Space: local partner self-service area for recipients, candidates,
and partner profile data.
  • API routes: backend endpoints used by the website and the recipient mobile
app.
  • Database layer: Prisma ORM with PostgreSQL.
  • Infrastructure: Terraform configuration under website/infra.
  • Tests: unit tests and Playwright end-to-end tests.

recipients_app/

Mobile app for recipients. Recipients can log in, view payment history, and complete surveys. See recipients_app/README.md for mobile setup details.

seed/

Seed data for the local Firebase emulators. Firebase Auth users are imported automatically when the local development environment starts.

Local Development Setup

Requirements

Install these tools before starting:

  • mise
  • Docker
  • Node.js and npm through mise
On macOS, install mise with:
brew install mise

1. Install Tool Versions And Dependencies

cd website
mise install
npm ci

The web app keeps its Node dependencies, mise tasks, formatting config, Prisma setup, and most local tooling inside website/.

2. Prepare Environment Variables

Copy the local env template:

cd website
cp .env.local.sample .env.local

For most external contributors, the only required CMS value is:

STORYBLOK_PREVIEW_TOKEN="<public-content-delivery-api-token>"

Despite the name, STORYBLOK_PREVIEW_TOKEN is used by the website to load Storyblok content through the Content Delivery API. A public token is enough for frontend and UI work against published content.

If you need this token, ask a maintainer or contact [email protected]. Do not commit real API keys or secrets.

Maintainers may also need these Storyblok values for preview mode, webhooks, campaign submissions, or schema/type generation:

  • STORYBLOK_PREVIEW_SECRET
  • STORYBLOK_WEBHOOK_SECRET
  • STORYBLOK_MANAGEMENT_TOKEN
  • STORYBLOK_PERSONAL_ACCESS_TOKEN
  • STORYBLOK_SPACE_ID

3. Start The Local Environment

cd website
mise dev

This starts:

  • PostgreSQL in Docker
  • Firebase emulators for Auth and Firestore
  • Next.js at http://localhost:3000
  • Storybook at http://localhost:6006
We use Storybook for reusable website UI components. It is started locally by mise dev and is available on staging and production at https://staging.socialincome.org/storybook and https://socialincome.org/storybook.

The Firebase emulator UI is available at:

http://localhost:4000

Auth users can be inspected at:

http://localhost:4000/auth

4. Seed The Local Database

Firebase Auth users are imported automatically from seed/auth_export when the emulator starts. The PostgreSQL database needs to be seeded once manually:

cd website
npm run db:seed

This fills the local database with representative test data from website/src/lib/database/seed.

To also create database entries for Storyblok campaigns (so campaign pages join CMS content with local donation data), run:

cd website
npm run db:seed:cms-campaigns:apply

This is create-only: it adds missing campaigns matched by Storyblok portalSlug and skips rows that already exist. Use npm run db:seed:cms-campaigns:apply:all to include unlisted campaigns, or npm run db:seed:cms-campaigns for a dry-run.

Requires STORYBLOK_PREVIEW_TOKEN in .env.local (see .env.local.sample).

Local Login

Open the website at:

http://localhost:3000

Click Login in the top navigation and enter one of these local test users:

| Area | Purpose | Email | | --- | --- | --- | | Portal | Internal operations and admin tool | [email protected] | | Dashboard | Contributor self-service area | [email protected] | | Partner Space | Local partner self-service area | [email protected] |

In staging and production, login sends a magic link by email. Locally, the Firebase emulator logs the magic link instead. Copy it from the terminal running mise dev, or open:

http://localhost:4000/logs

Development Flow

The main integration branch for active development is main.

  1. Create your feature branch from main.
  2. Keep your changes focused on one issue or feature.
  3. Run the relevant checks locally.
  4. Open a pull request back into main.
  5. Wait for CI and review.
Example:
git checkout main
git pull
git checkout -b fix/issue-2064-short-description

Website checks run for pull requests and for pushes to main. Staging deployment is connected to main; production releases are handled by maintainers.

Useful local checks for website changes:

cd website
npm run lint
npm run typecheck
npm run test:unit
npm run test:e2e

For many small UI or content changes, lint and typecheck are a good minimum before opening a PR. Run the broader test suite when touching shared logic, authentication, database behavior, or user flows.

Storyblok Development

We use Storyblok as CMS for parts of the public website.

For normal local development, set the public Content Delivery API token in website/.env.local:

STORYBLOK_PREVIEW_TOKEN="<public-content-delivery-api-token>"

Use local HTTPS if you are working with Storyblok live preview:

cd website
mise run dev-ssl

Storyblok Type Generation

If you changed the Storyblok schema, regenerate the generated TypeScript types. This requires maintainer-level Storyblok credentials:

cd website
npm run storyblok:generate

The command logs into Storyblok, pulls component schemas, and writes generated types to website/src/generated/storyblok/types.

Storyblok Management Token

Campaign submissions and other Management API calls require a Personal Access Token (PAT) with write access. This is separate from STORYBLOK_PERSONAL_ACCESS_TOKEN, which the Storyblok CLI uses for schema pull and type generation.

To create or rotate the token:

  1. Log in to Storyblok with [email protected].
  2. Open Account Settings → Personal Access Tokens (PAT).
  3. Create a token named Campaign management token with these scopes:
- Assets: read, write - Stories: read, write, publish - Asset folders: read - Spaces: read
  1. Set the token lifetime to 1 year.
  2. Copy the token immediately. Storyblok only shows it once.
Store the token in these places:
  • Local development: website/.env.local
STORYBLOK_MANAGEMENT_TOKEN="<token>"
  • 1Password: Social Income maintainer vault (for team access and rotation).
  • GitHub Actions (staging and production deploys): repository secrets
TF_STAGING_STORYBLOK_MANAGEMENT_TOKEN and TF_PROD_STORYBLOK_MANAGEMENT_TOKEN.

Terraform passes the secret to Cloud Run as STORYBLOK_MANAGEMENT_TOKEN at deploy time. Set a calendar reminder to rotate the token before it expires.

Anonymous Campaign Submissions

Visitors can submit campaigns from the public /campaigns page. Submissions:

  • create an inactive, non-public database Campaign with a server-generated slug
  • upload a primary image and create an unpublished Storyblok Campaign story
  • link database and CMS entries through Campaign.slug ↔ Storyblok.content.portalSlug
Publication happens manually in Storyblok. Published Storyblok stories are the sole public visibility gate for campaign pages (detail load and overview join).

Public active vs inactive is derived from campaign end date and goal progress (not the database isActive flag): a campaign is inactive when its finish date has passed or its goal amount has been reached. The overview filter and card linkability use that derived state; deep links to published stories still work after a campaign becomes inactive.

Server-only configuration lives in website/src/lib/config/campaign-submission.config.ts. Local development and deployed environments need STORYBLOK_MANAGEMENT_TOKEN; see Storyblok Management Token for creation and storage.

The token must be able to list assets in the default-images folder, create draft stories under pages/campaigns, and upload assets in the configured asset folder. If a submission fails after partial progress, the API attempts compensating cleanup of the created Storyblok asset, Storyblok story, and database row.

Future hardening (not part of the first version): Cloudflare Turnstile and distributed rate limiting on POST /api/campaign-submissions.

Mobile API

The recipients_app communicates with the Next.js API routes. The public API documentation is available at:

https://socialincome.org/v1/api-docs

Monitoring

The website pages Slack (#social-income-monitoring) for production failures that must not stay silent:

  • Cloud Run logs that contain SLACK_ALERT (Stripe webhooks, payment
imports, scheduler jobs). Prefix console.error with that token. Staging still writes the logs but does not page Slack, because its Stripe and campaign data is incomplete. At most one Slack message is sent every 5 minutes.
  • Uptime checks every 60s: /api/health/website, /api/health/database,
and the public homepage /en/int.
  • Cloud Run 5xx bursts, Cloud Scheduler job errors, Cloud SQL CPU and
connections, and Cloud Run memory / OOM.

The recipients app still reports errors to Sentry.

Troubleshooting

Translations Or Generated Content Look Stale

rm -rf website/.next
cd website
mise dev

Firebase Seed Data Did Not Update

The Firebase emulators load seed data from seed/. If you changed the seed data and want a fresh start:

docker compose -f website/docker-compose.yml down --remove-orphans --volumes
cd website
mise dev

Docker Or Database State Looks Broken

If Prisma migrations fail, old containers are hanging around, or the local DB is in a strange state, reset the website Docker environment:

docker compose -f website/docker-compose.yml down --remove-orphans --volumes

This removes the website Docker containers and named volumes, including local PostgreSQL data. Run mise dev and npm run db:seed again afterwards.

E2E Checks Look Stuck

The Playwright CI job may update screenshots and commit them back into a PR. That creates a new commit. GitHub sometimes does not start a fresh workflow run for commits made by github-actions, so checks can appear stale even though the previous run passed. Ask a maintainer if this happens.

Useful Commands

Database

cd website
npm run db:seed
npm run db:seed:cms-campaigns:apply
npm run db:studio
npm run db:migrate:dev

Dump Local Database

pg_dump -Fc --no-owner "postgresql://social-income:social-income@localhost:5432/social-income" > local.dump

Restore A Dump

pg_restore --clean --if-exists --no-owner -d "<database-url>" local.dump

Financial Contributions

Donate 1 Percent Of Your Income

Become a contributor of Social Income. Donations are tax-deductible in Switzerland.

Sponsor Dev Community

Become a sponsor and help build open-source software for more equality and less poverty. Donations through the GitHub Sponsor program support the developer community.

Social Income NGO

Non-Profit Organization

Social Income is a non-profit association (CHE-289.611.695) based in Zurich, Switzerland. Connect with us on X, Instagram, LinkedIn, Facebook, or by email.

Radical Transparency

We believe that transparency builds trust and trust builds solidarity. This is why we disclose our finances to the public.

Open Source Community

Open source is made by people like you. These individuals, among many others, have contributed to Social Income:

Contributors</a>

Software And IP Contributions

We receive in-kind donations from Google Nonprofit, GitHub, Codemagic, Cloudflare, Linktree, Twilio, Algolia, JetBrains, Storyblok, 1Password, Mux, Sentry, and Lineto. Our tools also use open-source technologies such as FireCMS, Storybook, and Tailwind CSS.

License

This project is licensed under MIT, with the exception of the Unica77 font, which is exclusively licensed to Social Income.

Chat with me