Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

# Cancel in-progress runs for the same branch/PR to avoid wasted compute
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
name: Lint & Type-check
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run ESLint
run: pnpm lint

- name: Run TypeScript type-check
run: pnpm type-check

test:
name: Test & Coverage
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Run tests with coverage
run: pnpm test:ci --coverage --coverageReporters=json-summary --coverageReporters=text

- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: coverage/
retention-days: 7

# Only update the badge on pushes to main (not on PRs)
- name: Generate coverage badge
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: tj-actions/coverage-badge-js@v2
with:
output: coverage-badge.svg

- name: Commit coverage badge
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: update coverage badge [skip ci]"
file_pattern: coverage-badge.svg
commit_user_name: github-actions[bot]
commit_user_email: github-actions[bot]@users.noreply.github.com
53 changes: 53 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Deploy Cloudflare Worker

on:
push:
branches: [main]
paths:
- "workers/**"
- "wrangler.supabase.toml"
- ".github/workflows/deploy.yml"

# Prevent concurrent deploys — only one at a time, never cancel mid-deploy
concurrency:
group: deploy-worker
cancel-in-progress: false

jobs:
deploy:
name: Deploy Supabase Worker
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
run_install: false

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Deploy worker to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --config wrangler.supabase.toml
secrets: |
SUPABASE_URL
SUPABASE_API_KEY
WORKER_SHARED_SECRET
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_API_KEY: ${{ secrets.SUPABASE_API_KEY }}
WORKER_SHARED_SECRET: ${{ secrets.WORKER_SHARED_SECRET }}
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
PlotlineAI is a group movie recommendation app. Each participant shares their tastes -- favourite film, preferred era, current mood, and a favourite film personality -- and the system uses **embedding-based vector search** combined with a **language model** to surface movies the whole group will enjoy.

[![Live Demo](https://img.shields.io/badge/demo-plotline--ai-blue)](https://plotline-ai.vercel.app/)
[![CI](https://github.com/CodeHunt101/plotline-ai/actions/workflows/ci.yml/badge.svg)](https://github.com/CodeHunt101/plotline-ai/actions/workflows/ci.yml)
![Coverage](./coverage-badge.svg)

## Table of Contents

Expand All @@ -12,6 +14,7 @@ PlotlineAI is a group movie recommendation app. Each participant shares their ta
- [Getting Started](#getting-started)
- [Project Structure](#project-structure)
- [Cloudflare Workers](#cloudflare-workers)
- [CI/CD](#cicd)
- [AI Limitations](#ai-limitations)

---
Expand Down Expand Up @@ -72,7 +75,7 @@ graph TD
SvcTMDB --> TMDB
```

> Full diagrams — React component tree and AI fallback circuit breaker → [`docs/diagrams.md`](./docs/diagrams.md)
> Full diagrams — React component tree, AI fallback circuit breaker, and CI/CD pipeline → [`docs/diagrams.md`](./docs/diagrams.md)

---

Expand Down Expand Up @@ -329,6 +332,31 @@ To enable it:

The workflow also supports manual runs from the **Actions** tab via `workflow_dispatch`.

## CI/CD

This project uses GitHub Actions for continuous integration and automated Cloudflare Worker deploys.

### Workflows

| Workflow | Trigger | What it does |
| ------------------------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci.yml` | Push / PR → `main` | ESLint, TypeScript type-check, Jest (95% coverage threshold). Uploads a coverage report artifact and auto-commits a coverage badge to `main`. |
| `deploy.yml` | Push → `main` (worker files only) | Deploys the Supabase Cloudflare Worker via Wrangler. |
| `supabase-keepalive.yml` | Daily schedule | Runs a keepalive query against Supabase so the free-tier project stays active. |

### Required GitHub secrets

Open **Settings → Secrets and variables → Actions** in your GitHub repo and add the following secrets:

| Secret | Used by | Where to find it |
| ----------------------- | ------------------------ | --------------------------------------------------- |
| `SUPABASE_DB_URL` | `supabase-keepalive.yml` | Supabase dashboard → Connect → Transaction mode |
| `CLOUDFLARE_API_TOKEN` | `deploy.yml` | Cloudflare dashboard → My Profile → API Tokens |
| `CLOUDFLARE_ACCOUNT_ID` | `deploy.yml` | Cloudflare dashboard → right-hand sidebar |
| `SUPABASE_URL` | `deploy.yml` | Supabase dashboard → Project Settings → Data API |
| `SUPABASE_API_KEY` | `deploy.yml` | Supabase dashboard → Project Settings → Data API |
| `WORKER_SHARED_SECRET` | `deploy.yml` | Must match `SUPABASE_WORKER_SECRET` in `.env.local` |

## Project Structure

```
Expand Down
35 changes: 35 additions & 0 deletions docs/diagrams.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,38 @@ flowchart TD
ValidJSON -- No --> ExtractHeuristics["fallbackMovieRecommendations<br/>Extract titles from vector text"]
ExtractHeuristics --> ReturnRec
```

---

## 5. CI/CD Pipeline

Full lifecycle from a code change to production, via GitHub Actions and Vercel.

```mermaid
flowchart TD
Dev(["👨‍💻 Developer\npushes code"])

Dev --> PR["Open Pull Request\n→ main"]
Dev --> DirectPush["Direct push\n→ main"]

PR --> CI_PR["ci.yml\nLint · Type-check · Tests"]
CI_PR --> CIResult{"All checks\npassed?"}

CIResult -- "❌ Fail" --> Block["PR blocked\nfix and re-push"]
Block --> PR

CIResult -- "✅ Pass" --> VercelPreview["Vercel\nPreview Deployment\nunique URL per PR"]
VercelPreview --> Review["Code review\n+ preview testing"]
Review --> Merge["Merge PR → main"]

DirectPush --> MainPush["Push lands on main"]
Merge --> MainPush

MainPush --> CI_Main["ci.yml\nLint · Type-check · Tests\n+ update coverage badge"]
MainPush --> VercelProd["Vercel\nProduction Deployment\nplotline-ai.vercel.app"]

CI_Main --> WorkerChanged{"Worker files\nchanged?"}
WorkerChanged -- No --> Done(["✅ Done"])
WorkerChanged -- Yes --> Deploy["deploy.yml\nWrangler deploy\nCloudflare Worker"]
Deploy --> Done
```
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "plotline-ai",
"version": "0.1.0",
"private": true,
"packageManager": "pnpm@10.15.1",
"scripts": {
"dev": "next dev",
"build": "next build",
Expand All @@ -12,6 +13,7 @@
"test": "jest",
"test:ci": "jest --ci",
"test:coverage": "jest --coverage",
"validate:push": "pnpm type-check && pnpm lint && pnpm test:coverage",
"prepare": "husky"
},
"lint-staged": {
Expand Down
47 changes: 47 additions & 0 deletions types/images.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Type declarations for static image imports (PNG, JPG, SVG, WebP, AVIF, ICO, BMP, GIF).
// Next.js generates these via next-env.d.ts at runtime, but that file is gitignored.
// This file ensures tsc resolves image imports correctly in CI and other non-Next.js contexts.
declare module "*.png" {
const value: import("next/image").StaticImageData;
export default value;
}

declare module "*.jpg" {
const value: import("next/image").StaticImageData;
export default value;
}

declare module "*.jpeg" {
const value: import("next/image").StaticImageData;
export default value;
}

declare module "*.webp" {
const value: import("next/image").StaticImageData;
export default value;
}

declare module "*.avif" {
const value: import("next/image").StaticImageData;
export default value;
}

declare module "*.svg" {
const value: import("next/image").StaticImageData;
export default value;
}

declare module "*.gif" {
const value: import("next/image").StaticImageData;
export default value;
}

declare module "*.ico" {
const value: import("next/image").StaticImageData;
export default value;
}

declare module "*.bmp" {
const value: import("next/image").StaticImageData;
export default value;
}
Loading