diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..63a66ea --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: CI + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + backend: + name: Backend compile check + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Compile backend modules + run: python -m compileall -q backend + + frontend: + name: Frontend lint, typecheck, and build + runs-on: ubuntu-latest + + defaults: + run: + working-directory: frontend + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Typecheck + run: npx tsc --noEmit + + - name: Build + run: npm run build diff --git a/README.md b/README.md index f77c0ef..810f955 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,16 @@ python3 app.py ```bash cd frontend npm install + +# Optional: point the frontend at a non-local backend API. +cp .env.example .env.local + npm run dev ``` +The frontend reads `NEXT_PUBLIC_API_BASE_URL` from `frontend/.env.local`. +The default is `http://127.0.0.1:5000/` for local development. + ### 4. Access the Application - Frontend: http://localhost:3000 @@ -141,6 +148,12 @@ Analyzes a GitHub repository and its description for SDG alignment using Aurora 4. Add tests if applicable 5. Submit a pull request +## Deployment + +See [Deployment Readiness Plan](docs/deployment-readiness.md) for the current +hosting recommendation, CI checks, and follow-up work for the DMP 2026 MVP +requirements in issue #8. + ## License See LICENSE file for details. diff --git a/docs/deployment-readiness.md b/docs/deployment-readiness.md new file mode 100644 index 0000000..31bcedc --- /dev/null +++ b/docs/deployment-readiness.md @@ -0,0 +1,64 @@ +# Deployment Readiness Plan + +This document maps the current application to the hosting and CI/CD part of +[DMP 2026 issue #8](https://github.com/chaoss/UNSDG-classifier-tool/issues/8). +It does not close the full MVP issue; the DPG registry evaluation and accuracy +work remain separate milestones. + +## Recommended Hosting Shape + +The current architecture is a split application: + +- Next.js frontend in `frontend/` +- Flask backend in `backend/` +- External classifier services called from the backend +- GitHub API access for repository metadata + +Because GitHub Pages cannot run the Flask backend or Python ML dependencies, a +static-only GitHub Pages deployment is not sufficient for the full tool. A +practical zero-budget MVP path is: + +1. Host the Next.js frontend on a static/Node-capable frontend host. +2. Host the Flask backend on a Python-capable host. +3. Configure the frontend with `NEXT_PUBLIC_API_BASE_URL` so staging and + production can point at different backend URLs without code changes. +4. Keep classifier endpoints, tokens, and rate-limit-sensitive settings outside + source code through environment variables. + +## Configuration + +Frontend configuration: + +```bash +cd frontend +cp .env.example .env.local +# Set NEXT_PUBLIC_API_BASE_URL to the hosted Flask API origin. +``` + +Backend configuration should keep external API endpoints and credentials in the +host environment. If PR #23 is merged first, the backend will have a centralized +`backend/config.py` and `backend/.env.example` for these values. + +## CI/CD Baseline + +This repository now includes `.github/workflows/ci.yml`, which runs on every +pull request and push to `main`: + +- Python backend compile check with Python 3.11 +- Frontend dependency install from `package-lock.json` +- ESLint +- TypeScript typecheck +- Next.js production build + +That gives maintainers a merge gate before connecting a deployment provider. + +## Follow-Up Work For #8 + +- Add a backend health endpoint for deployment monitoring. +- Add backend tests around API request/response contracts. +- Add deployment provider-specific instructions once maintainers choose the + final no-budget hosting platform. +- Add the DPG registry evaluation spreadsheet/notebook for the 100-project and + 85% accuracy acceptance criteria. +- Add API throttling/backoff around Aurora and OSDG calls to preserve the + documented one-request-per-second limit. diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..85827e9 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,4 @@ +# Copy this file to frontend/.env.local when the backend API is hosted +# somewhere other than the local Flask default. + +NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:5000/ diff --git a/frontend/.gitignore b/frontend/.gitignore index 5ef6a52..7b8da95 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel diff --git a/frontend/services/api.ts b/frontend/services/api.ts index 006d226..e6c8e22 100644 --- a/frontend/services/api.ts +++ b/frontend/services/api.ts @@ -1,11 +1,16 @@ import axios from "axios"; import { - ResultsData, SDGClassificationRequest, SDGClassificationResponse, } from "@/types/main"; -const API_BASE_URL = "http://127.0.0.1:5000/"; +const DEFAULT_API_BASE_URL = "http://127.0.0.1:5000/"; + +const normalizeBaseUrl = (url: string) => (url.endsWith("/") ? url : `${url}/`); + +const API_BASE_URL = normalizeBaseUrl( + process.env.NEXT_PUBLIC_API_BASE_URL || DEFAULT_API_BASE_URL, +); const apiClient = axios.create({ baseURL: API_BASE_URL, diff --git a/frontend/types/global.d.ts b/frontend/types/global.d.ts index cbe652d..0ffc89f 100644 --- a/frontend/types/global.d.ts +++ b/frontend/types/global.d.ts @@ -1 +1,22 @@ declare module "*.css"; + +declare module "*.png" { + import type { StaticImageData } from "next/image"; + + const content: StaticImageData; + export default content; +} + +declare module "*.jpg" { + import type { StaticImageData } from "next/image"; + + const content: StaticImageData; + export default content; +} + +declare module "*.jpeg" { + import type { StaticImageData } from "next/image"; + + const content: StaticImageData; + export default content; +} diff --git a/frontend/types/main.d.ts b/frontend/types/main.d.ts index 4b051b7..f7ae450 100644 --- a/frontend/types/main.d.ts +++ b/frontend/types/main.d.ts @@ -47,7 +47,7 @@ export interface SDGClassificationResponse { projectName?: string; projectUrl?: string; repo_url?: string; - [key: string]: any; + [key: string]: unknown; } export type SDGCardProps = {