# Project export: ReadOn

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: We are helping special needs students learn how to read by developing phonics and understanding sentences via images and voice comprehension.
- Devpost: https://devpost.com/software/readon
- GitHub: https://github.com/AsadShahid04/ReadOn
- Video: https://www.youtube.com/embed/Ec_yoK5_ZGI?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Asad Shahid (8 commits), Aadhil M. Syed (8 commits), Yousef Saad El Din (3 commits)

## Devpost submission (written by the team)

### Inspiration

We were inspired by the need for better accessibility tools for students with special needs. Observing the struggle of these students in accessing reading materials, and the lack of adaptive technologies in schools to support their unique learning requirements, motivated us to develop a solution. Our frustration stemmed from the gap in resources that should be helping these students succeed. ReadOn was born out of a desire to bridge this gap and provide an inclusive tool that empowers students with special needs to access educational content more effectively.

### What it does

ReadOn is an accessible reading assistant designed to cater to students with different learning abilities. It simplifies text, generates voiceovers, and uses adaptive reading speeds based on the user’s needs. The app can highlight key information, break down complex paragraphs, and convert text to audio for students who struggle with traditional reading methods. By using ReadOn, students can customize their reading experience to suit their personal needs, improving comprehension and retention of material.

### How we built it

We built ReadOn using Next.js for the frontend, ensuring a fast and responsive user experience. The app integrates with natural language processing (NLP) APIs to provide text simplification and summary features. We used text-to-speech libraries to convert text into audio, making the content accessible for users with visual impairments or dyslexia. The backend is powered by Node.js. We also focused on making the user interface intuitive and easy to navigate, ensuring that students and educators can use the tool without a steep learning curve.

### Challenges we ran into

One of the major challenges we faced was integrating an accurate and responsive text-to-speech feature that could handle a wide range of educational content without errors. Specifically, we encountered difficulties with phonetic breakdowns, as many words have multiple pronunciations or complex phonetic structures. Ensuring that the text-to-speech system correctly pronounced these words based on context proved to be a significant hurdle. Additionally, optimizing the application’s performance when processing large documents in real-time, while ensuring that all accessibility features like accurate phonetics were properly implemented, presented further obstacles. Balancing these needs without sacrificing user experience was one of our biggest technical hurdles.

### Accomplishments we're proud of

We’re proud of building an application that can truly make a difference in the lives of students with special needs. Creating an adaptable, multi-functional reading tool that simplifies content while keeping it engaging and accessible is an achievement we’re excited about. Additionally, we successfully integrated key accessibility features like audio generation, customizable reading levels, and visual aids that enhance the overall user experience. Most importantly, we are proud that this tool can empower students who have previously struggled with traditional learning methods.

### What we learned

Throughout this journey, we learned the importance of accessibility in education and how small changes in technology can significantly impact the learning experience for students with special needs. We also deepened our understanding of text processing, audio synthesis, and front-end optimization to create a seamless and supportive user experience. Additionally, we gained valuable insight into how inclusive design principles can be applied to software development, ensuring that the app caters to a diverse set of users.

### What's next

Looking ahead, we plan to incorporate AI-driven personalization to automatically adjust reading levels and recommend content based on each user’s progress. We aim to further improve text summarization and make the app compatible with a wider range of devices, including mobile phones and tablets. Our next steps include building a comprehensive dashboard for teachers, where they can monitor their students’ progress and provide personalized guidance. Finally, we hope to collaborate with schools and educational institutions to bring ReadOn into classrooms and assist a larger population of students in their learning journey.

## README (from the GitHub repository)

# Read On

Read On is a reading support platform with four learner-facing capabilities:

- phonics and pronunciation support
- reading comprehension activities
- story visualization
- audiobook/read-aloud playback

The app uses a story-centric flow: generate a story once, then access all features by `storyId` from `/features/[storyId]`.

## Repository Structure

```text
ReadOn/
├── src/app/             # Next.js routes and API handlers
├── views/               # UI components and page views
├── orchestrators/       # BFF/service composition layer
├── microservices/       # phonics, comprehension, image-generation, audiobook, dashboard
├── shared/              # shared clients, types, constants
├── ops/gcp/             # deploy/provision/verify scripts
└── docs/                # architecture and operations docs
```

## Core Architecture

- **Frontend:** Next.js + Chakra UI
- **Backend-for-Frontend:** orchestrators in the Next app
- **Services:** independent microservices for each feature area
- **Data ownership:** story and feature data are loaded by `storyId`, with route-level ownership checks in BFF APIs

## Running the App (local dev)

The Next app and microservices run as separate processes.

1. Install dependencies:

```bash
npm install
npm --prefix microservices/phonics-service install
npm --prefix microservices/comprehension-service install
npm --prefix microservices/image-generation-service install
npm --prefix microservices/audiobook-service install
npm --prefix microservices/dashboard-service install
```

2. Copy env templates and configure values:

```bash
cp .env.example .env
cp microservices/phonics-service/.env.example microservices/phonics-service/.env
cp microservices/comprehension-service/.env.example microservices/comprehension-service/.env
cp microservices/image-generation-service/.env.example microservices/image-generation-service/.env
cp microservices/audiobook-service/.env.example microservices/audiobook-service/.env
cp microservices/dashboard-service/.env.example microservices/dashboard-service/.env
```

3. Start services:

```bash
npm run dev:all
```

Or start individually with `npm run dev`, `npm run dev:phonics`, `npm run dev:comprehension`, `npm run dev:image-generation`, `npm run dev:audiobook`, and `npm run dev:dashboard`.

### Local Ports

| Process | Port |
| --- | --- |
| Main Next.js app | 3000 |
| phonics-service | 3001 |
| comprehension-service | 3002 |
| image-generation-service | 3003 |
| audiobook-service | 3004 |
| dashboard-service | 3005 |

## Main Routes

- `/` (story input + generate)
- `/dashboard`
- `/features/[storyId]` (feature hub)
- `/phonics`, `/comprehension`, `/visualization`, `/audiobook` (feature entry pages)
- `/audiobook/player`
- `/story/[id]`
- `/auth`

## Validation Commands

```bash
npm run lint
npm run test
npm run verify:services
npm run verify:endpoints
```

## Deployment (Google Cloud Run)

Deployment is script-driven from `ops/gcp/` (no built-in auto-deploy workflow in this repo).

- Provision: `bash ops/gcp/provision.sh`
- Deploy stack: `READON_DEPLOY_ENV=prod|test SERVICE_VERSION=<git-sha> bash ops/gcp/deploy-stack.sh`
- Verify: `READON_DEPLOY_ENV=prod|test bash ops/gcp/verify/verify-health.sh`

## Documentation

- Architecture: [docs/architecture.md](docs/architecture.md)
- Deployment guide: [docs/deployment-guide.md](docs/deployment-guide.md)
- CI/CD overview: [docs/cicd-overview.md](docs/cicd-overview.md)
- Env reference: [docs/environment-variables.md](docs/environment-variables.md)


## Detected evidence (automated analysis)

Indexed codebase: 267 recognized source files, 654 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- PostgreSQL (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- AI coding agent: Cursor — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 295)

```
.dockerignore
.env.example
.eslintrc.json
.gitignore
Dockerfile
docs/architecture.md
docs/cicd-overview.md
docs/db/schema.sql
docs/deployment-guide.md
docs/environment-separation.md
docs/environment-variables.md
docs/github-actions-setup.md
docs/infrastructure-overview.md
docs/patterns/api-composition.md
docs/service-access-model.md
docs/storage-migration.md
microservices/audiobook-service/.env.example
microservices/audiobook-service/controllers/audiobookController.ts
microservices/audiobook-service/Dockerfile
microservices/audiobook-service/models/audiobookModel.ts
microservices/audiobook-service/package.json
microservices/audiobook-service/README.md
microservices/audiobook-service/routes/index.ts
microservices/audiobook-service/server.ts
microservices/audiobook-service/services/audiobookService.ts
microservices/audiobook-service/services/audiobookTtsCache.ts
microservices/audiobook-service/services/googleTtsSynthesis.ts
microservices/audiobook-service/tsconfig.json
microservices/common/httpServer.js
microservices/common/logger.js
microservices/comprehension-service/.env.example
microservices/comprehension-service/config/index.js
microservices/comprehension-service/controllers/comprehensionController.js
microservices/comprehension-service/db/client.js
microservices/comprehension-service/db/migrate.js
microservices/comprehension-service/db/migrations/001_comprehension_persistence.sql
microservices/comprehension-service/db/migrations/002_align_comprehension_schema.sql
microservices/comprehension-service/db/migrations/003_relax_legacy_comprehension_columns.sql
microservices/comprehension-service/Dockerfile
microservices/comprehension-service/package.json
microservices/comprehension-service/README.md
microservices/comprehension-service/repositories/comprehensionRepository.js
microservices/comprehension-service/routes/index.js
microservices/comprehension-service/server.js
microservices/comprehension-service/services/circuitBreaker.js
microservices/comprehension-service/services/circuitBreaker/CircuitBreakerState.js
microservices/comprehension-service/services/circuitBreaker/ClosedState.js
microservices/comprehension-service/services/circuitBreaker/HalfOpenState.js
microservices/comprehension-service/services/circuitBreaker/OpenState.js
microservices/comprehension-service/services/comprehensionService.js
microservices/comprehension-service/services/fallbackProvider.js
microservices/comprehension-service/services/inMemoryQuestionCache.js
microservices/comprehension-service/services/llmClient.js
microservices/comprehension-service/services/llmProvider.js
microservices/comprehension-service/swagger/comprehensionservice.yaml
microservices/comprehension-service/tests/unit/circuitBreaker.test.js
microservices/comprehension-service/tests/unit/fallbackProvider.test.js
microservices/dashboard-service/.env.example
microservices/dashboard-service/controllers/creditsController.js
microservices/dashboard-service/controllers/historyController.js
microservices/dashboard-service/controllers/readerStoriesController.js
microservices/dashboard-service/db/pools.js
microservices/dashboard-service/Dockerfile
microservices/dashboard-service/models/creditsModel.js
microservices/dashboard-service/models/readerStoryModel.js
microservices/dashboard-service/models/storyModel.js
microservices/dashboard-service/package.json
microservices/dashboard-service/README.md
microservices/dashboard-service/routes/credits.js
microservices/dashboard-service/routes/history.js
microservices/dashboard-service/routes/readerStories.js
microservices/dashboard-service/server.js
microservices/image-generation-service/.dockerignore
microservices/image-generation-service/.env.example
microservices/image-generation-service/.gitignore
microservices/image-generation-service/cache/IImageGenerationCache.ts
microservices/image-generation-service/cache/InMemoryImageCache.ts
microservices/image-generation-service/clients/CloudStorageClient.ts
microservices/image-generation-service/clients/createImageProvider.ts
microservices/image-generation-service/clients/ExternalAIImageClient.ts
microservices/image-generation-service/clients/IImageProvider.ts
microservices/image-generation-service/clients/OpenAIImageClient.ts
microservices/image-generation-service/clients/VertexGeminiImageClient.ts
microservices/image-generation-service/config.ts
microservices/image-generation-service/controllers/imageGenerationController.ts
microservices/image-generation-service/docker-compose.yml
microservices/image-generation-service/Dockerfile
microservices/image-generation-service/errors/ImageGenerationError.ts
microservices/image-generation-service/models/ImageGenerationMetadata.ts
microservices/image-generation-service/package.json
microservices/image-generation-service/repositories/IImageMetadataRepository.ts
microservices/image-generation-service/repositories/InMemoryImageMetadataRepository.ts
microservices/image-generation-service/repositories/PostgresImageMetadataRepository.ts
microservices/image-generation-service/routes/index.ts
microservices/image-generation-service/server.ts
microservices/image-generation-service/services/IImageGenerationService.ts
microservices/image-generation-service/services/ImageGenerationProxy.ts
microservices/image-generation-service/services/RealImageGenerationService.ts
microservices/image-generation-service/services/storybookFromStoryService.ts
microservices/image-generation-service/tsconfig.json
microservices/image-generation-service/types/dtos.ts
microservices/image-generation-service/types/enums.ts
microservices/image-generation-service/utils/cacheKeyGenerator.ts
microservices/image-generation-service/utils/logger.ts
microservices/image-generation-service/utils/rateLimiter.ts
microservices/image-generation-service/utils/requestIdGenerator.ts
microservices/image-generation-service/utils/retryPolicy.ts
microservices/image-generation-service/utils/splitIllustrationParagraphs.ts
microservices/phonics-service/.env.example
microservices/phonics-service/adapters/merriamWebsterAdapter.ts
microservices/phonics-service/adapters/merriamWebsterIntermediate.types.ts
microservices/phonics-service/controllers/phonicsController.ts
microservices/phonics-service/db/migrations/000_reset_phonics.sql
microservices/phonics-service/db/migrations/001_init_phonics.sql
microservices/phonics-service/Dockerfile
microservices/phonics-service/models/db.ts
microservices/phonics-service/models/phonicsModel.ts
microservices/phonics-service/package.json
microservices/phonics-service/providers/phoneticsProvider.ts
microservices/phonics-service/README.md
[175 more files omitted for size]
```

### Dependencies

- microservices/audiobook-service/package.json: @google-cloud/text-to-speech@^6.4.0, @types/express@^4.17.21, @types/node@^20.0.0, dotenv@^17.4.2, express@^4.21.2, tsx@^4.19.2, typescript@^5.0.0
- microservices/comprehension-service/package.json: @google-cloud/vertexai@^1.10.0, dotenv@^17.4.2, pg@^8.20.0
- microservices/dashboard-service/package.json: dotenv@^17.4.2, express@^4.21.2, pg@^8.20.0
- microservices/image-generation-service/package.json: @google-cloud/storage@^7.19.0, @google-cloud/vertexai@^1.10.0, @types/cors@^2.8.19, @types/express@^4.17.21, @types/node@^20.0.0, @types/pg@^8.10.0, cors@^2.8.6, dotenv@^16.3.1, express@^4.18.2, pg@^8.11.0, tsx@^4.7.0, typescript@^5.0.0
- microservices/phonics-service/package.json: @types/express@^4.17.21, @types/node@^20.0.0, @types/pg@^8.20.0, compromise@^14.15.0, dotenv@^17.4.2, express@^4.21.2, pg@^8.20.0, tsx@^4.19.2, typescript@^5.0.0, zod@^3.24.1
- package.json: @chakra-ui/react@^2.10.4, @emotion/react@^11.13.5, @emotion/styled@^11.13.5, @types/bcryptjs@^2.4.6, @types/express@^5.0.6, @types/node@^20, @types/pg@^8.20.0, @types/react@^18, @types/react-dom@^18, bcryptjs@^3.0.3, compromise@^14.15.0, concurrently@^9.1.2, dotenv@^17.4.2, eslint@^8, eslint-config-next@14.0.4, express@^5.2.1, jose@^6.2.2, next@^14.2.10, pg@^8.20.0, react@18.2.0, react-dom@18.2.0, react-icons@^5.3.0, react-pageflip@^2.0.3, tsx@^4.19.2, typescript@^5, vitest@^2.1.8, zod@^3.24.1

### Recent commits (newest first)

- Merge pull request #2 from aadhilmsyed/migration
- chore(gcp): wire Vertex env vars into microservice deploy scripts
- feat(comprehension): add Vertex Gemini LLM provider for question generation
- feat(image-generation): add Vertex Gemini Nano Banana image provider
- chore(gcp): enable Vertex AI and grant aiplatform.user to runtime SAs
- Merge pull request #1 from aadhilmsyed/migration
- fix(story): reconcile visualization status when scenes persist after timeout
- fix(gcp): resolve Cloud Run service URLs when deploying main
- fix(image-generation): switch to gpt-image-1 and persist base64 images to GCS
- fix(image-generation): skip in-memory cache for story-scoped generations
- chore(gcp): update infra docs and defaults for readon-ai project
- chore(ci): remove sigrid workflow
- chore(read-on): update README
- Merge pull request #10 from amubarak-cmu-F25/patch/Tyler
- api composition adaption
- finishing touches
- Merge branch 'main' of github.com:amubarak-cmu-F25/ReadOn
- Fix caching for audiobookTTS
- fix: update gcloud deployment
- Merge pull request #8 from amubarak-cmu-F25/comprehension-module-refactor

## Key source files (fetched from GitHub, selected and truncated for size)

### docs/service-access-model.md

```markdown
# ReadOn Service Access Model

This document describes the default service access model for **prod** and **test** stacks.

## Public (browser-facing UI shell)

- **Prod:** `readon-main`
- **Test:** `readon-main-test`

These are deployed with unauthenticated access enabled.

## Microservices

**Prod:** `readon-phonics`, `readon-comprehension`, `readon-visualization`, `readon-audiobook`, `readon-dashboard`

**Test:** `readon-phonics-test`, `readon-comprehension-test`, `readon-visualization-test`, `readon-audiobook-test`, `readon-dashboard-test`

Deploy behavior is controlled by `READON_MICROSERVICES_PUBLIC` in `ops/gcp/deploy-microservice.sh`:

- `true` (default): grant `allUsers` invoker (public endpoints) and also keep main service-account invoker.
- `false`: remove `allUsers` invoker and keep only main service-account invoker.

## Health endpoints

All services expose `/health`, `/live`, `/ready`.

Depending on `READON_MICROSERVICES_PUBLIC`, external unauthenticated calls may return either `200` (public) or `401`/`403` (invoker-only). Control-plane health can remain healthy in either model.

```

### docs/storage-migration.md

```markdown
# Storage Bucket Migration (Hardening)

## Why this change

The original bucket name produced by the first deployment was incorrect:

- Old: `readon-readon-<project-id>-us-central1`

This pass fixes naming by moving to:

- New: `readon-<project-id>-assets` (e.g., `readon-492106-assets`)

The **test** stack uses a separate bucket: `readon-<project-id>-assets-test` (see [environment-separation.md](environment-separation.md)).

## Migration performed (runtime-safe)

1. Create new bucket:
   - `readon-492106-assets`
   - location `us-central1`
   - uniform bucket-level access
2. Copy existing objects:
   - `gsutil -m rsync -r gs://<old> gs://<new>`
3. Update Cloud Run environment:
   - `READON_STORAGE_BUCKET=<new-bucket>`
4. IAM tightening:
   - grant each service account `roles/storage.objectViewer` on the new bucket
   - remove broad `roles/storage.objectAdmin` grants from the old bucket

## Old bucket

The old bucket is **deprecated** and left in place.

Optional safe delete command (run only when you are sure it’s unused):

```bash
gcloud storage buckets delete gs://readon-readon-492106-us-central1
```


```

### Dockerfile

```
FROM node:20-slim AS builder

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:20-slim AS runner

WORKDIR /app

ENV NODE_ENV=production
ENV PORT=8080

COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next

EXPOSE 8080

CMD ["npm","run","start"]


```

### package.json

```
{
  "name": "readon",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "install:all": "npm install && npm --prefix microservices/phonics-service install && npm --prefix microservices/comprehension-service install && npm --prefix microservices/image-generation-service install && npm --prefix microservices/audiobook-service install && npm --prefix microservices/dashboard-service install",
    "dev": "next dev -p 3000",
    "build": "next build",
    "start": "next start -H 0.0.0.0 -p ${PORT:-3000}",
    "dev:phonics": "npm --prefix microservices/phonics-service run dev",
    "dev:comprehension": "npm --prefix microservices/comprehension-service run dev",
    "dev:image-generation": "npm --prefix microservices/image-generation-service run dev",
    "dev:audiobook": "npm --prefix microservices/audiobook-service run dev",
    "dev:dashboard": "npm --prefix microservices/dashboard-service run dev",
    "dev:all": "concurrently -n main,phonics,comp,img,ab,db -c blue,green,yellow,magenta,cyan,red \"npm run dev\" \"npm run dev:phonics\" \"npm run dev:comprehension\" \"npm run dev:image-generation\" \"npm run dev:audiobook\" \"npm run dev:dashboard\"",
    "verify:services": "bash scripts/verify-local-service-health.sh",
    "verify:endpoints": "bash scripts/verify-all-endpoints.sh",
    "lint": "next lint",
    "test": "vitest run",
    "test:watch": "vitest",
    "test:comprehension": "node --test microservices/comprehension-service/tests/unit/circuitBreaker.test.js",
    "phonics:db:bootstrap": "npx tsx microservices/phonics-service/scripts/bootstrap-db.ts",
    "phonics:db:reset": "npx tsx microservices/phonics-service/scripts/reset-phonics-schema.ts"
  },
  "dependencies": {
    "@chakra-ui/react": "^2.10.4",
    "@emotion/react": "^11.13.5",
    "@emotion/styled": "^11.13.5",
    "@types/express": "^5.0.6",
    "bcryptjs": "^3.0.3",
    "compromise": "^14.15.0",
    "express": "^5.2.1",
    "jose": "^6.2.2",
    "next": "^14.2.10",
    "pg": "^8.20.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-icons": "^5.3.0",
    "react-pageflip": "^2.0.3",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@types/bcryptjs": "^2.4.6",
    "@types/node": "^20",
    "@types/pg": "^8.20.0",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "concurrently": "^9.1.2",
    "dotenv": "^17.4.2",
    "eslint": "^8",
    "eslint-config-next": "14.0.4",
    "tsx": "^4.19.2",
    "typescript": "^5",
    "vitest": "^2.1.8"
  }
}

```

### microservices/image-generation-service/docker-compose.yml

```yaml
services:
  image-generation-service:
    build:
      context: .
    ports:
      - "3001:3001"
    env_file:
      - .env
    environment:
      PORT: 3001
    restart: unless-stopped

```

### microservices/comprehension-service/package.json

```
{
  "name": "comprehension-service",
  "version": "1.0.0",
  "private": true,
  "description": "ReadOn comprehension microservice",
  "main": "server.js",
  "scripts": {
    "dev": "node server.js",
    "start": "NODE_ENV=production node server.js"
  },
  "dependencies": {
    "@google-cloud/vertexai": "^1.10.0",
    "dotenv": "^17.4.2",
    "pg": "^8.20.0"
  }
}

```

### microservices/dashboard-service/package.json

```
{
  "name": "dashboard-service",
  "version": "1.0.0",
  "private": true,
  "description": "ReadOn dashboard microservice (stories, credits, history)",
  "main": "server.js",
  "scripts": {
    "dev": "node server.js",
    "start": "NODE_ENV=production node server.js"
  },
  "dependencies": {
    "dotenv": "^17.4.2",
    "express": "^4.21.2",
    "pg": "^8.20.0"
  }
}

```

### microservices/phonics-service/Dockerfile

```
# Build from repository root (see ops/gcp/build-image.sh).
FROM node:22-alpine

WORKDIR /app/microservices/phonics-service

ENV NODE_ENV=production
ENV PORT=8080
ENV HOST=0.0.0.0
ENV SERVICE_NAME=phonics-service

COPY microservices/phonics-service/package.json microservices/phonics-service/package-lock.json ./
RUN npm ci --omit=dev

COPY microservices/phonics-service/ ./

EXPOSE 8080

CMD ["npx", "tsx", "server.ts"]

```

### microservices/audiobook-service/Dockerfile

```
# Build from repository root (see ops/gcp/build-image.sh).
FROM node:22-alpine

WORKDIR /app/microservices/audiobook-service

ENV NODE_ENV=production
ENV PORT=8080
ENV HOST=0.0.0.0
ENV SERVICE_NAME=audiobook-service

COPY microservices/audiobook-service/package.json microservices/audiobook-service/package-lock.json ./
RUN npm ci --omit=dev

COPY microservices/audiobook-service/ ./

EXPOSE 8080

CMD ["npx", "tsx", "server.ts"]

```

### microservices/dashboard-service/Dockerfile

```
# Build from repository root (see ops/gcp/build-image.sh).
FROM node:22-alpine

WORKDIR /app/microservices/dashboard-service

ENV NODE_ENV=production
ENV PORT=8080
ENV HOST=0.0.0.0
ENV SERVICE_NAME=dashboard-service

COPY microservices/dashboard-service/package.json microservices/dashboard-service/package-lock.json ./
RUN npm ci --omit=dev --no-audit --no-fund

COPY microservices/dashboard-service/ ./

EXPOSE 8080

CMD ["node", "server.js"]

```

[263 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]