From b76368d6e513c66604526f2fa4bb60478b05930b Mon Sep 17 00:00:00 2001 From: Temirkhan Amanzhanov Date: Mon, 27 Apr 2026 19:47:50 +0200 Subject: [PATCH] Add CLAUDE.md; strict TS config; Vite env fixes Add a new .claude/CLAUDE.md with repository guidance for Claude Code. Tighten AffirmationGenerator.Client/tsconfig.node.json by enabling many stricter TypeScript checks (noUnusedLocals/Parameters, noFallthroughCasesInSwitch, noUncheckedIndexedAccess, noImplicitOverride, jsx/target, path alias, etc.) and normalize the include. Update vite.config.ts to use bracket-style env access (env["VAR"]) and normalize APPDATA/HOME usage when resolving local HTTPS cert paths to avoid potential runtime/typing issues. --- .claude/CLAUDE.md | 101 ++++++++++++++++++ .../tsconfig.node.json | 24 ++++- AffirmationGenerator.Client/vite.config.ts | 14 +-- 3 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 .claude/CLAUDE.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..a258c24 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,101 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +AffirmationGenerator is a fullstack app that serves daily positive affirmations with multi-language support (via DeepL) and per-IP rate limiting (via Redis). It is deployed as a single Docker container to Fly.io. + +## Commands + +### Frontend (`AffirmationGenerator.Client/`) + +```bash +pnpm run dev # Start Vite dev server (port 5173) +pnpm run build # TypeScript check + Vite build +pnpm run lint # ESLint (max-warnings=0 — must be clean) +pnpm run format # Prettier formatting +``` + +### Backend (`AffirmationGenerator.Server/`) + +```bash +dotnet run # Start API (https://localhost:7006) + SPA proxy +dotnet build AffirmationGenerator.slnx +dotnet test AffirmationGenerator.slnx # Run all unit tests +dotnet test --filter "FullyQualifiedName~ClassName" # Run a single test class +``` + +### Docker + +```bash +docker build -t affirmation-generator . +docker run -p 8080:8080 affirmation-generator +``` + +## Architecture + +### Stack +- **Frontend**: React 19 + Vite + TypeScript + Tailwind CSS v4 + DaisyUI + TanStack React Query + Axios +- **Backend**: ASP.NET Core 10.0 with Clean Architecture +- **External services**: affirmations.dev (source), DeepL API (translation), Redis (rate limiting) + +### How the pieces connect + +In development, `dotnet run` starts the .NET server which launches the Vite dev server as a SPA proxy. API calls from Vite are proxied to `https://localhost:7006`. In production, the multi-stage Docker build compiles the React app and embeds it as static files in `wwwroot`; the .NET server serves both the SPA and the API. + +### Backend layer structure (Clean Architecture) + +| Layer | Responsibility | +|---|---| +| `Api/` | Controllers, rate limiting policy, HTTP models | +| `Application/` | CQRS-style query handlers, service orchestration | +| `Infrastructure/` | External HTTP clients (Refit), Redis client | +| `Domain/` | `AffirmationLanguage` enum, domain errors | +| `Core/` | `Result` error-handling pattern, shared extensions | + +Each layer has its own `DiConfig.cs` for DI registration; all are wired in `Program.cs`. + +### API endpoints + +| Method | Path | Description | +|---|---|---| +| GET | `/affirmations?targetLanguage={lang}` | Fetch affirmation (returns `{text, remainingCount}`) | +| GET | `/affirmations/remaining` | Remaining quota and `resetInSeconds` | +| GET | `/affirmations/languages` | Supported language codes | +| GET | `/health` | Health check | +| GET | `/swagger` | Swagger UI (dev only) | + +### Rate limiting + +Fixed-window limiter keyed on client IP (configurable via `Application:ClientOptions:ClientIpHeaderName` for proxy scenarios). Default: 10 requests/day per IP. Counter stored in Redis; falls back to in-memory if Redis is unavailable. + +### Error handling + +The backend uses a `Result` type (`Core/Result.cs`) — all query handlers return `Result` instead of throwing. Controllers map domain errors to HTTP responses. + +## Local Development Setup + +1. Set the DeepL API key via user secrets: + ```bash + dotnet user-secrets set "Infrastructure:DeepLTranslatorClientOptions:ApiKey" "YOUR_KEY" \ + --project AffirmationGenerator.Server + ``` +2. Redis is optional locally — the app falls back gracefully. +3. Swagger UI is available at `https://localhost:7006/swagger` in dev. + +## Configuration Keys + +``` +Application:ClientOptions:MaxRequestsPerDay # Default: 10 +Application:ClientOptions:ClientIpHeaderName # Header name for real IP (e.g. Fly.io) +Infrastructure:DeepLTranslatorClientOptions:ApiKey +Infrastructure:AffirmationClientOptions:BaseUrl +Infrastructure:RedisClientOptions:ConnectionString +``` + +## CI/CD + +- **PR to main** → `build-test.yml` (restore → build → test) +- **Push to main** → `fly-deploy.yml` (build-test + deploy to Fly.io) +- Pre-commit hooks via `dotnet husky` (defined in `.husky/`) diff --git a/AffirmationGenerator.Client/tsconfig.node.json b/AffirmationGenerator.Client/tsconfig.node.json index 97ede7e..308889d 100644 --- a/AffirmationGenerator.Client/tsconfig.node.json +++ b/AffirmationGenerator.Client/tsconfig.node.json @@ -5,7 +5,27 @@ "module": "ESNext", "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, - "strict": true + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "allowUnusedLabels": false, + "noUncheckedSideEffectImports": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "allowUnreachableCode": false, + "noUncheckedIndexedAccess": true, + "noPropertyAccessFromIndexSignature": true, + "erasableSyntaxOnly": true, + "noImplicitOverride": true, + "jsx": "react-jsx", + "target": "ESNext", + "paths": { + "@/*": [ + "./src/*" + ] + } }, - "include": ["vite.config.ts"] + "include": [ + "vite.config.ts" + ] } diff --git a/AffirmationGenerator.Client/vite.config.ts b/AffirmationGenerator.Client/vite.config.ts index eb7283e..a4f5101 100644 --- a/AffirmationGenerator.Client/vite.config.ts +++ b/AffirmationGenerator.Client/vite.config.ts @@ -8,10 +8,10 @@ import path from "path"; import child_process from "child_process"; import { env } from "process"; -const target = env.ASPNETCORE_HTTPS_PORT - ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` - : env.ASPNETCORE_URLS - ? env.ASPNETCORE_URLS.split(";")[0] +const target = env["ASPNETCORE_HTTPS_PORT"] + ? `https://localhost:${env["ASPNETCORE_HTTPS_PORT"]}` + : env["ASPNETCORE_URLS"] + ? env["ASPNETCORE_URLS"].split(";")[0] : "https://localhost:7006"; // https://vitejs.dev/config/ @@ -21,9 +21,9 @@ export default defineConfig(({ command }) => { // Setup https certificates for local development if (command === "serve") { const baseFolder = - env.APPDATA !== undefined && env.APPDATA !== "" - ? `${env.APPDATA}/ASP.NET/https` - : `${env.HOME}/.aspnet/https`; + env["APPDATA"] !== undefined && env["APPDATA"] !== "" + ? `${env["APPDATA"]}/ASP.NET/https` + : `${env["HOME"]}/.aspnet/https`; const certificateName = "reactapp1.client"; const certFilePath = path.join(baseFolder, `${certificateName}.pem`);