Skip to content

Commit d86085d

Browse files
committed
chore: add sample code review
1 parent 5694fa6 commit d86085d

90 files changed

Lines changed: 6777 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
node_modules/
2+
dist/
3+
apps/api/dist/
4+
apps/api/data/
5+
apps/api/secrets/
6+
apps/web/dist/
7+
.env
8+
.env.local
9+
apps/api/.env
10+
apps/api/.env.local
11+
apps/web/.env
12+
apps/web/.env.local
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# `sample-code-review`
2+
3+
Monorepo **inside rockets**: the layout is inspired by [rockets-starter](https://github.com/btwld/rockets-starter) (`apps/api` + `apps/web`), but the backend uses **`workspace:^`** — local packages in `packages/*`, **not** the starter's npm versions such as `@bitwild/rockets@1.0.0-alpha.7`.
4+
5+
```text
6+
rockets/ ← SDK monorepo (source of truth)
7+
├── packages/rockets-core, rockets-server, …
8+
└── examples/sample-code-review/
9+
├── apps/
10+
│ ├── api/ NestJS + local Rockets (`api`) :3001
11+
│ └── web/ Vite + React (`web`) :3000
12+
└── packages/typescript-config/ (shared TS only)
13+
```
14+
15+
## Authentication (Firebase → token → Rockets server)
16+
17+
Firebase is **not** the application backend. It only issues the **ID token**; the **Rockets server** validates that token on **every** request.
18+
19+
```text
20+
Web (Firebase Auth) API (@bitwild/rockets)
21+
───────────────── ───────────────────────
22+
email/password login → (does not participate in login)
23+
getIdToken() → Authorization: Bearer <Firebase JWT>
24+
→ AuthServerGuard (global)
25+
→ FirebaseAuthAdapter.validateToken()
26+
→ request.user = AuthorizedUser (uid, email, roles…)
27+
→ GET /me, /github/*, /analysis/*
28+
```
29+
30+
| Layer | Responsibility |
31+
|-------|----------------|
32+
| **Firebase (client)** | Web login; `user.getIdToken()` |
33+
| **`@bitwild/rockets-adapter-firebase`** | Admin SDK / verifier: `verifyIdToken` |
34+
| **`@bitwild/rockets` (`RocketsModule`)** | `APP_GUARD` + `MeController` + protected routes |
35+
| **Your controllers** | `@AuthUser()`, `@Ctx()` — user already authenticated |
36+
37+
API config: `RocketsModule.forRoot({ auth: defineFirebaseAuth(), … })` with `authProviderExternallyManaged: true` (the user lives in Firebase, not in the local signup table).
38+
39+
**For real tokens to work:** the same `projectId` must be used in web (`VITE_FIREBASE_PROJECT_ID`) and API (`FIREBASE_PROJECT_ID=rockets-review-demo`). The service account JSON is **optional** in development — the Admin SDK can start with only `projectId`. `FIREBASE_USE_FAKE` must stay **disabled** in `apps/api/.env`.
40+
41+
Quick verification after web login (DevTools → Network → any API call) or:
42+
43+
```bash
44+
curl -H "Authorization: Bearer <firebase-id-token>" http://localhost:3001/me
45+
```
46+
47+
## Local SDK (required)
48+
49+
`apps/api/package.json` declares:
50+
51+
```json
52+
"@bitwild/rockets": "workspace:^",
53+
"@bitwild/rockets-core": "workspace:^",
54+
"@bitwild/rockets-adapter-firebase": "workspace:^"
55+
```
56+
57+
Yarn resolves these to `packages/rockets-server`, `packages/rockets-core`, and so on — the same codebase used by [`sample-server`](../sample-server/).
58+
59+
**Before the first `dev`**, build the parent monorepo packages:
60+
61+
```bash
62+
# from the rockets repository root
63+
yarn build
64+
```
65+
66+
## Quick start
67+
68+
### 1. API (`apps/api/.env`)
69+
70+
```bash
71+
cp apps/api/.env.example apps/api/.env
72+
```
73+
74+
**GitHub OAuth App → Authorization callback URL:**
75+
76+
```text
77+
http://localhost:3000/auth/github/callback
78+
```
79+
80+
It must match `GITHUB_OAUTH_CALLBACK_URL` in the API `.env`.
81+
82+
**Firebase Admin:** `apps/api/secrets/firebase-service-account.json`
83+
84+
### 2. Web (`apps/web/.env`)
85+
86+
```bash
87+
cp apps/web/.env.example apps/web/.env
88+
```
89+
90+
```env
91+
VITE_API_URL=http://localhost:3001
92+
VITE_FIREBASE_API_KEY=...
93+
VITE_FIREBASE_AUTH_DOMAIN=...
94+
VITE_FIREBASE_PROJECT_ID=...
95+
VITE_FIREBASE_APP_ID=...
96+
```
97+
98+
### 3. Run API + Web
99+
100+
From the **rockets root** (recommended):
101+
102+
```bash
103+
yarn build
104+
yarn workspace sample-code-review dev
105+
```
106+
107+
Or only inside the example:
108+
109+
```bash
110+
cd examples/sample-code-review
111+
yarn dev
112+
```
113+
114+
| App | URL |
115+
|-----|-----|
116+
| Web | http://localhost:3000 |
117+
| API | http://localhost:3001 |
118+
| Swagger | http://localhost:3001/api |
119+
120+
### 4. Flow
121+
122+
1. Sign in with Firebase (email/password)
123+
2. Connect GitHub → callback `/auth/github/callback`
124+
3. Choose a repo → Run code review (GitHub API + OpenAI `gpt-4o-mini` when `OPENAI_API_KEY` or `OPEN_API_KEY` is present in `apps/api/.env`)
125+
4. Open the report
126+
127+
**OpenAI (optional, inexpensive for testing):**
128+
129+
```env
130+
OPENAI_API_KEY=sk-... # or OPEN_API_KEY
131+
OPENAI_MODEL=gpt-4o-mini
132+
```
133+
134+
## Two persistence backends
135+
136+
| Data | Backend | Config |
137+
|------|---------|--------|
138+
| GitHub OAuth / connection, `userMetadata` | **SQLite** (TypeORM via `repository` in `RocketsModule`) | `DATABASE_PATH` or `:memory:` |
139+
| Code review reports | **Firestore** via `@bitwild/rockets-repository-firestore` | `FIREBASE_FIRESTORE_REPORTS_COLLECTION` (default: `code_review_reports`) |
140+
141+
`CodeReviewReportEntity` declares `repository: FirestoreRepositoryModule` in its bundle — the same per-entity override pattern Rockets uses for TypeORM. Services use `@InjectDynamicRepository` + `RepositoryInterface`, not custom storage code.
142+
143+
Each report is stored as a document in `code_review_reports/{reportId}`. The list endpoint supports API filters:
144+
145+
- `GET /analysis/reports?github=org/repo` — GitHub repository
146+
- `GET /analysis/reports?q=text` — search in `fullName` and `summary`
147+
- `GET /analysis/reports?status=completed` — job status
148+
149+
E2E uses `FIREBASE_FIRESTORE_USE_FAKE=true` (in-memory Firestore). In production, enable **Cloud Firestore** in the Firebase Console (Native mode).
150+
151+
New monorepo package: `packages/rockets-repository-firestore` (mirrors the role played by `@concepta/nestjs-repository-typeorm`).
152+
153+
## Scripts
154+
155+
| Command | Description |
156+
|---------|-------------|
157+
| `yarn dev` | API + Web in parallel (`concurrently`) |
158+
| `yarn dev:api` | API only |
159+
| `yarn dev:web` | Web only |
160+
| `yarn build` | Build both apps |
161+
| `yarn test:e2e` | API E2E (test fakes enabled) |
162+
163+
From the **rockets** root:
164+
165+
| Command | Description |
166+
|---------|-------------|
167+
| `yarn sample-code-review:dev` | `yarn build` (local SDK) + `dev` |
168+
| `yarn sample-code-review:test:e2e` | build + e2e |
169+
170+
## Difference vs `rockets-starter`
171+
172+
| `rockets-starter` (GitHub) | This example |
173+
|----------------------------|--------------|
174+
| `@bitwild/rockets@1.0.0-alpha.7` from npm | `workspace:^` → local `packages/*` |
175+
| Built-in `@bitwild/rockets-auth` | Firebase via `defineFirebaseAuth()` |
176+
| Next.js web | Vite + React (same ports 3000/3001) |
177+
| PostgreSQL | SQLite (like `sample-server`) |
178+
179+
## Related
180+
181+
- [`../sample-server/`](../sample-server/) — canonical Rockets config
182+
- [`apps/api/src/app.module.ts`](apps/api/src/app.module.ts)`RocketsModule.forRoot`
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Server
2+
PORT=3001
3+
APP_PUBLIC_URL=http://localhost:3001
4+
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001
5+
6+
# Persist SQLite between restarts (optional; default :memory:)
7+
# DATABASE_PATH=./data/code-review.sqlite
8+
9+
# Firebase — required for real Web tokens (must match VITE_FIREBASE_PROJECT_ID)
10+
FIREBASE_PROJECT_ID=rockets-review-demo
11+
# Recommended for Firestore + Auth in local dev (Admin SDK)
12+
# FIREBASE_SERVICE_ACCOUNT_PATH=./secrets/firebase-service-account.json
13+
# See apps/api/FIREBASE.md
14+
15+
# Firestore — code review reports (second persistence backend)
16+
# FIREBASE_FIRESTORE_REPORTS_COLLECTION=code_review_reports
17+
18+
# E2E only — do not use in real runs
19+
# FIREBASE_USE_FAKE=true
20+
# FIREBASE_FIRESTORE_USE_FAKE=true
21+
# GITHUB_USE_FAKE=true
22+
23+
# GitHub OAuth App — https://github.com/settings/developers
24+
GITHUB_CLIENT_ID=
25+
GITHUB_CLIENT_SECRET=
26+
# Must match "Authorization callback URL" in the GitHub OAuth App exactly
27+
# Must match the web app route (and GitHub OAuth App callback URL)
28+
GITHUB_OAUTH_CALLBACK_URL=http://localhost:3000/auth/github/callback
29+
# Space-separated scopes (repo = private + public repos for the authenticated user)
30+
GITHUB_OAUTH_SCOPES=read:user repo
31+
32+
# OpenAI code review (optional — gpt-4o-mini is cheap for demos)
33+
# OPENAI_API_KEY=sk-...
34+
OPENAI_MODEL=gpt-4o-mini
35+
# OPENAI_REVIEW_MAX_FILES=15
36+
# OPENAI_REVIEW_MAX_CHARS=80000
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.env
2+
.env.local
3+
dist/
4+
data/
5+
secrets/
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Firebase in `sample-code-review`
2+
3+
| Product | Role |
4+
|---------|------|
5+
| **Firebase Auth** | Web login; API validates the ID token |
6+
| **Cloud Firestore** | Reports via `@bitwild/rockets-repository-firestore` + `RepositoryInterface` |
7+
8+
GitHub connection data and profile metadata use **SQLite** (TypeORM, default adapter in `RocketsModule`).
9+
10+
## Rockets registration (backend-agnostic pattern)
11+
12+
```typescript
13+
// analysis.feature.ts
14+
defineModuleResource({
15+
entities: [{ entity: CodeReviewReportEntity, repository: FirestoreRepositoryModule }],
16+
providers: [AnalysisService],
17+
});
18+
```
19+
20+
```typescript
21+
// analysis.service.ts
22+
constructor(
23+
@InjectDynamicRepository(CodeReviewReportEntity)
24+
private readonly reportRepo: RepositoryInterface<CodeReviewReportEntity>,
25+
) {}
26+
```
27+
28+
Firestore collection: `code_review_reports` (env `FIREBASE_FIRESTORE_REPORTS_COLLECTION`).
29+
30+
## Console
31+
32+
1. Enable Authentication.
33+
2. Enable Firestore Database (Native mode).
34+
3. Add a service account in `apps/api/secrets/firebase-service-account.json` (recommended).
35+
36+
## API `.env`
37+
38+
```env
39+
FIREBASE_PROJECT_ID=rockets-review-demo
40+
# Real Firestore — required for persisted reports (Auth alone is not enough):
41+
FIREBASE_SERVICE_ACCOUNT_PATH=./secrets/firebase-service-account.json
42+
# FIREBASE_FIRESTORE_REPORTS_COLLECTION=code_review_reports
43+
```
44+
45+
**Auth vs Firestore:** Firebase token validation can work with only `FIREBASE_PROJECT_ID`. **Persisting and listing Firestore reports requires a service account** (JSON in `apps/api/secrets/`).
46+
47+
Without the JSON, the sample automatically enables `FIREBASE_FIRESTORE_USE_FAKE=true` (in-memory reports) and logs a warning.
48+
49+
E2E uses `FIREBASE_FIRESTORE_USE_FAKE=true` explicitly in the test setup.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"moduleFileExtensions": ["js", "json", "ts"],
3+
"testEnvironment": "node",
4+
"testRegex": ".*\\.e2e-spec\\.ts$",
5+
"testPathIgnorePatterns": ["/node_modules/", "/dist/"],
6+
"roots": ["<rootDir>/test"],
7+
"testTimeout": 30000,
8+
"maxWorkers": 1,
9+
"transform": {
10+
"^.+\\.ts$": ["ts-jest", { "tsconfig": "<rootDir>/tsconfig.json" }]
11+
}
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"collection": "@nestjs/schematics",
3+
"sourceRoot": "src",
4+
"compilerOptions": {
5+
"tsConfigPath": "tsconfig.json"
6+
}
7+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"name": "api",
3+
"private": true,
4+
"version": "0.0.0",
5+
"description": "sample-code-review API — NestJS + Rockets (Firebase auth, GitHub OAuth, code review).",
6+
"scripts": {
7+
"build": "tsc -p tsconfig.json",
8+
"dev": "nest start --watch",
9+
"start": "nest start",
10+
"type-check": "tsc --noEmit -p tsconfig.json",
11+
"lint": "echo \"lint: configure eslint when needed\"",
12+
"clean": "rm -rf dist",
13+
"test:e2e": "jest --config jest-e2e.config.json",
14+
"test:e2e:auth": "jest --config jest-e2e.config.json --testPathPattern=auth-connection"
15+
},
16+
"dependencies": {
17+
"@bitwild/rockets": "workspace:^",
18+
"@bitwild/rockets-adapter-firebase": "workspace:^",
19+
"@bitwild/rockets-common": "workspace:^",
20+
"@bitwild/rockets-core": "workspace:^",
21+
"@bitwild/rockets-repository": "workspace:^",
22+
"@bitwild/rockets-repository-firestore": "workspace:^",
23+
"@concepta/nestjs-repository-typeorm": "8.0.0-alpha.5",
24+
"@nestjs/common": "11.1.18",
25+
"@nestjs/config": "4.0.0",
26+
"@nestjs/core": "11.1.18",
27+
"@nestjs/cqrs": "11.0.3",
28+
"@nestjs/platform-express": "11.1.18",
29+
"@nestjs/swagger": "11.2.5",
30+
"@nestjs/typeorm": "11.0.0",
31+
"class-transformer": "^0.5.1",
32+
"class-validator": "^0.14.1",
33+
"firebase-admin": "^13.0.0",
34+
"helmet": "^8.0.0",
35+
"reflect-metadata": "^0.1.14",
36+
"rxjs": "^7.8.1",
37+
"sqlite3": "^5.1.7",
38+
"typeorm": "^0.3.20"
39+
},
40+
"devDependencies": {
41+
"@nestjs/cli": "^11.0.0",
42+
"@nestjs/testing": "11.1.18",
43+
"@types/jest": "^29.5.0",
44+
"@types/node": "^18.19.44",
45+
"@types/supertest": "^6.0.2",
46+
"jest": "^29.7.0",
47+
"supertest": "^6.3.4",
48+
"ts-jest": "^29.2.0",
49+
"ts-node": "^10.9.2",
50+
"typescript": "^5.4.0"
51+
},
52+
"resolutions": {
53+
"@nestjs/common": "11.1.18",
54+
"@nestjs/core": "11.1.18",
55+
"@nestjs/platform-express": "11.1.18"
56+
}
57+
}

0 commit comments

Comments
 (0)