Tutorial

Providers & Deployment

A complete walkthrough of every external service OCM depends on, followed by deploying the frontend to Vercel and the backend to Render. Start here after completing Getting Started.

How OCM's Auth Works

Understanding the auth flow makes every other setup step clearer.

  1. 1

    User signs in

    Firebase SDK handles sign-in (email/password or social). Firebase issues a short-lived ID token.

  2. 2

    Token attached to every API call

    The Axios interceptor in frontend/src/api/axiosInstance.js automatically fetches the current Firebase token and adds it as Authorization: Bearer <token>.

  3. 3

    Django verifies the token

    FirebaseAuthentication in backend/userapp/authentication.py calls firebase_admin.auth.verify_id_token(). If valid, it looks up or creates a matching CustomUser row.

  4. 4

    Request proceeds normally

    DRF sees an authenticated request. Role/group checks (IsLeaderOrReadOnly, IsAuthorOrPastor) run on top of this.

This means Firebase is the only auth dependency you can't skip. Cloudinary, Paystack, and YouTube are optional for basic functionality.

Firebase — Auth & Firestore

Firebase handles two things in OCM: user authentication (sign up, sign in, token refresh) and Firestore (real-time group chat). You need one Firebase project for both.

Create the Firebase project

  1. 1. Go to console.firebase.google.com and create a new project.
  2. 2. Enable Google Analytics if you want it — it's not required by OCM.
  3. 3. In the left sidebar go to Build → Authentication → Get started.
  4. 4. Enable the Email/Password sign-in provider (and any others you want, e.g. Google).
  5. 5. Go to Build → Firestore Database → Create database. Start in production mode — you'll add Security Rules next.

Get the Web SDK config (frontend)

  1. 1. In Firebase Console go to Project Settings (gear icon) → General → Your apps.
  2. 2. Click Add app → Web. Register it (no Firebase Hosting needed).
  3. 3. Copy the firebaseConfig object. Each field maps to a VITE_ env var in frontend/.env.local.
// Firebase gives you this:
const firebaseConfig = {
  apiKey: "...",            // → VITE_FIREBASE_API_KEY
  authDomain: "...",        // → VITE_FIREBASE_AUTH_DOMAIN
  projectId: "...",         // → VITE_FIREBASE_PROJECT_ID
  storageBucket: "...",     // → VITE_FIREBASE_STORAGE_BUCKET
  messagingSenderId: "...", // → VITE_FIREBASE_MESSAGING_SENDER_ID
  appId: "...",             // → VITE_FIREBASE_APP_ID
};

Get the service account key (backend)

  1. 1. In Project Settings go to the Service Accounts tab.
  2. 2. Click "Generate new private key" and confirm.
  3. 3. Download the JSON file and save it as backend/firebase-key.json.
  4. 4. Never commit this file. It's already in .gitignore.

Firestore Security Rules

OCM uses Firestore directly from the frontend for chat. Paste these rules in Firestore → Rules to restrict reads/writes to authenticated users only:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Chat rooms: any authenticated user can read
    match /rooms/{roomId} {
      allow read: if request.auth != null;
      allow write: if request.auth != null;

      // Messages inside a room
      match /messages/{messageId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.uid == request.auth.uid;
        allow delete: if request.auth != null
                      && resource.data.uid == request.auth.uid;
      }
    }
  }
}

Cloudinary — Media Storage

Django uses Cloudinary as the default file storage backend (DEFAULT_FILE_STORAGE = 'cloudinary_storage.storage.MediaCloudinaryStorage'). All profile pictures, post images, and uploaded media go here.

  1. 1. Create a free account at cloudinary.com.
  2. 2. From the Dashboard copy your Cloud Name, API Key, and API Secret.
  3. 3. Add them to backend/.env:
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
The free tier gives you 25 GB storage and 25 GB bandwidth per month — more than enough for a small congregation. No credit card required.

Paystack — Payments

Paystack is used for charity organisation donations. The backend verifies payment via backend/mainapp/views.py → VerifyPaymentView using the secret key. The frontend initiates payment using the public key.

  1. 1. Create an account at paystack.com (available in Nigeria, Ghana, Kenya, South Africa, and more).
  2. 2. From Settings → API Keys & Webhooks copy your Test Secret Key and Test Public Key.
  3. 3. For local development always use the test keys — no real money moves.
  4. 4. Add the keys to your env files:
# backend/.env
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxx

# frontend/.env.local
VITE_PAYSTACK_PUBLIC_KEY=pk_test_xxxxxxxxxxxxxxxxxxxx
When you go to production, swap sk_test_sk_live_ and pk_test_pk_live_. Live keys require a verified Paystack business account.

YouTube Data API

The streaming page uses the YouTube Data API v3 to fetch live streams and videos. This key lives only in the frontend — the backend never touches it.

  1. 1. Go to console.cloud.google.com and create (or select) a project.
  2. 2. In the sidebar go to APIs & Services → Library. Search for YouTube Data API v3 and enable it.
  3. 3. Go to APIs & Services → Credentials → Create Credentials → API Key.
  4. 4. Copy the key and optionally restrict it to the YouTube Data API and your domain.
  5. 5. Add it to frontend/.env.local:
VITE_YOUTUBE_API_KEY=AIzaSy...
The free quota is 10,000 units/day. A search request costs 100 units, a video list costs 1. For a church streaming page this is rarely a concern.

PostgreSQL

The backend reads the database connection from a single DATABASE_URL environment variable (via dj-database-url), or from individual POSTGRES_* variables as a fallback.

Local development

Use a local PostgreSQL instance. Create the database then set the variables in backend/.env:

POSTGRES_DB=ocm
POSTGRES_USER=postgres
POSTGRES_PASSWORD=yourpassword
POSTGRES_HOST=localhost
POSTGRES_PORT=5432

Cloud database (any provider)

Set a single DATABASE_URL and it takes precedence:

DATABASE_URL=postgresql://user:pass@host:5432/db

Works with Render (auto-injected), Supabase, Railway, Neon, or any pg host.

When deploying to Render, the database URL is injected automatically — you don't set it manually. See the Render section below.

Deploy: Frontend to Vercel

The frontend is a static Vite SPA. frontend/vercel.json already includes the SPA rewrite rule so all routes resolve to index.html.

Deploy via Vercel CLI

npm i -g vercel
cd frontend
vercel

Vercel auto-detects Vite. Set the root directory to frontend when prompted.

Or deploy via the Vercel dashboard

  1. 1. Go to vercel.com and import your GitHub repository.
  2. 2. Set the Root Directory to "frontend" in the project settings.
  3. 3. Framework will be detected as Vite automatically.
  4. 4. Add all VITE_ environment variables from your .env.local in Project Settings → Environment Variables.
  5. 5. Deploy. Every push to main triggers a new deployment.

Point VITE_API_URL at your live backend

Once the backend is deployed (next step), update the environment variable in Vercel's dashboard:

VITE_API_URL=https://your-service.onrender.com/api/

Deploy: Backend to Render

The repo includes backend/render.yaml which provisions a Python web service and a free PostgreSQL database in one step. Render reads this file automatically when you connect the repo.

1. Connect the repo

  1. 1. Go to dashboard.render.com → New → Blueprint.
  2. 2. Connect your GitHub account and select this repository.
  3. 3. Render finds render.yaml and shows you the services it will create: "opencms" (web) and "cmsdb-service" (PostgreSQL).
  4. 4. Click Apply — Render creates both services.

2. Set environment variables

DATABASE_URL and SECRET_KEY are handled automatically by render.yaml. You need to set the rest manually in the web service's Environment tab:

ALLOWED_HOSTS=your-service.onrender.com
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=
PAYSTACK_SECRET_KEY=
BACKEND_ADMIN_EMAIL=
BACKEND_ADMIN_PASSWORD=

3. Upload firebase-key.json

Render doesn't support file uploads directly, so you have two options:

Option A — Secret file (recommended)

In Render, go to the service → Environment → Secret Files. Add a file named firebase-key.json with the contents of your service account JSON. Render mounts it at /etc/secrets/firebase-key.json. Update FIREBASE_PATH in settings.py to that path for production.

Option B — Env variable

Base64-encode the JSON file, store it as an env var (FIREBASE_KEY_B64), then decode it at startup in settings.py before calling firebase_admin.initialize_app().

4. What happens on deploy

Render runs build.sh which:

pip install -r requirements.txt
python manage.py collectstatic --no-input
python manage.py migrate

Then starts the server with:

python -m gunicorn church.asgi:application -k uvicorn.workers.UvicornWorker

Post-Deployment Checklist

After both services are live, there are a few cross-service wires to connect.

  • Add your Vercel URL to Django CORS

    In backend/church/settings.py, add your production Vercel URL to CORS_ALLOWED_ORIGINS. Then redeploy the backend.

    CORS_ALLOWED_ORIGINS = [
      "https://your-app.vercel.app",
      ...
    ]
  • Add your Render URL to Firebase authorised domains

    In Firebase Console → Authentication → Settings → Authorised domains, add your Render backend domain (e.g. your-service.onrender.com). This prevents token errors.

  • Update VITE_API_URL in Vercel

    Set VITE_API_URL to your Render service URL in Vercel's Environment Variables, then trigger a redeployment.

    VITE_API_URL=https://your-service.onrender.com/api/
  • Restrict your YouTube API key

    In Google Cloud Console, restrict the key to HTTP referrers and add your Vercel domain. This prevents quota abuse.

  • Switch Paystack to live keys

    When you're ready to accept real payments, replace the sk_test_ / pk_test_ keys with your live keys in both services.

You're done

OCM is live.

View the source, open issues, or contribute on GitHub.

View on GitHub →