Markdown Converter
Agent skill for markdown-converter
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Sign in to like and favorite skills
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
finan-track is a cloud-agnostic invoice processing and financial tracking application with WhatsApp integration via Kapso.ai. The architecture follows a layered functional style optimized for solo development—no classes, no over-abstraction, just clean TypeScript functions.
Routes (HTTP) → Services (Business Logic) → Repositories (Data) → Libraries (External Services)
Key architectural files to read:
LAYERED_ARCHITECTURE.md - Main architecture overview (START HERE)docs/PHASE_1_LAYERED.md - Detailed implementation guidedocs/ARCHITECTURE_COMPARISON.md - Why we chose this architecturedocs/APP_INFRA_SEPARATION.md - Application vs infrastructure separationdocs/ULTRACITE_SETUP.md - Code quality and linting setupapp/ ├── api/ # HTTP routes and middleware (Fastify) ├── services/ # Business logic (pure functions) ├── repositories/ # Data access layer (PostgreSQL) ├── jobs/ # Background workers ├── lib/ # External service clients (cloud-agnostic) │ ├── storage.ts # S3/R2/MinIO client │ ├── queue.ts # SQS/RabbitMQ client │ ├── whatsapp.ts # Kapso.ai client │ ├── ocr.ts # OCR service client │ └── cache.ts # Redis/KV client ├── types/ # TypeScript types ├── utils/ # Helper functions ├── config/ # Configuration management (Zod schemas) └── frontend/ # Dashboard UI (React + Vite + Tailwind) infra/terraform/ # Infrastructure as Code (Terraform) deployments/docker/ # Docker Compose for local dev migrations/ # Database migrations
# Start all services (PostgreSQL, MinIO, RabbitMQ, Redis) in Docker npm run dev:all # Start API server with hot reload npm run dev # Start background worker npm run dev:worker # Start frontend (in app/frontend/) cd app/frontend && npm run dev
# Format and auto-fix with Ultracite (replaces ESLint + Prettier) npm run format # Check code quality and complexity npm run lint # TypeScript type checking npm run typecheck # Run all checks (typecheck + lint + test) npm run check
# Run tests npm test # Run tests in watch mode npm run test:watch # Generate coverage report npm run test:coverage
# Build for production npm run build # Start production server npm run start # Start production worker npm run start:worker
# Run database migrations npm run migrate # Seed database with test data npm run seed
# Start all local services npm run docker:up # Stop all services npm run docker:down # Build Docker image npm run docker:build
All code uses pure functions instead of classes. This makes the codebase simpler, more testable, and easier to understand.
loadConfig() to initialize and getConfig() to accesslet s3Client: S3Client)init*() function to initialize and get*() function to accessQUEUE_TYPE=sqs|rabbitmq|memory)Example structure:
let client: Client; export function initClient(): Client { if (client) return client; // Initialize client return client; } function getClient(): Client { if (!client) initClient(); return client; } export async function doSomething(): Promise<Result> { const c = getClient(); // Use client }
query, queryOne, transaction from ./db.tsapp/types/)Naming convention:
createX(data) - Insert new recordgetXById(id) - Get by IDlistXs(filters) - List with filtersupdateX(id, data) - Update recorddeleteX(id) - Delete record (prefer soft delete)Example structure:
import * as repo from '../repositories/some-repo'; import * as lib from '../lib/some-lib'; export async function doBusinessLogic(input: Input): Promise<Output> { // 1. Validate input // 2. Call repositories and libraries // 3. Apply business rules // 4. Return result }
Example structure:
import { FastifyInstance } from 'fastify'; import * as service from '../../services/some-service'; export async function routes(app: FastifyInstance) { app.get('/:id', async (request, reply) => { const { id } = request.params; const result = await service.get(id); return result; }); }
The
app/lib/ directory contains cloud-agnostic clients that work with multiple providers by checking environment variables.
Example: Storage (
app/lib/storage.ts)
STORAGE_ENDPOINT, STORAGE_BUCKET, etc.Example: Queue (
app/lib/queue.ts)
QUEUE_TYPE env varenqueue(), dequeue(), consume()Example: Database (
app/repositories/db.ts)
DATABASE_URLThis project uses simple imports instead of a DI container. Functions are imported directly where needed.
// ✅ Good - Direct imports import * as storage from '../lib/storage'; import * as queue from '../lib/queue'; export async function processFile() { await storage.uploadFile('key', buffer); await queue.enqueue('jobs', { id: 123 }); }
// ❌ Avoid - DI container const container = new Container(); container.bind('storage').to(StorageService); // ... unnecessary complexity
{ success: boolean, error?: string }storage.uploadFile())This project uses Ultracite (not ESLint + Prettier).
# Format and auto-fix all issues npm run format # Check for issues (no auto-fix) npm run lint
Important: When making changes, run
npm run check before committing. This runs typecheck + lint + tests.
See
docs/ULTRACITE_SETUP.md for details.
The frontend uses a configurable theme system powered by Tailwind CSS. Colors can be customized by editing
theme.config.json.
primary - Main brand colorsecondary - Secondary UI elementsaccent - Accent highlightssuccess, warning, error - State colorsneutral - Text and neutral elementsAfter modifying
theme.config.json, rebuild the frontend:
cd app/frontend && npm run build
All configuration is done via environment variables. See
.env.example for all available options.
Database:
DATABASE_URL - PostgreSQL connection stringStorage (S3-compatible):
STORAGE_ENDPOINT - S3 endpoint (e.g., http://localhost:9000 for MinIO)STORAGE_BUCKET - Bucket nameSTORAGE_ACCESS_KEY, STORAGE_SECRET_KEY - CredentialsQueue:
QUEUE_TYPE - sqs, rabbitmq, or memoryQUEUE_URL - Queue connection stringWhatsApp:
KAPSO_API_KEY - Kapso.ai API keyKAPSO_WEBHOOK_SECRET - Webhook secretOCR:
OCR_PROVIDER - tesseract, textract, or cloudflare-aiTo switch from local MinIO to AWS S3, just update env vars:
# Local (MinIO) STORAGE_ENDPOINT=http://localhost:9000 STORAGE_BUCKET=invoices # Production (AWS S3) STORAGE_ENDPOINT=https://s3.amazonaws.com STORAGE_BUCKET=my-prod-bucket
No code changes needed! The application is fully cloud-agnostic.
LAYERED_ARCHITECTURE.md to understand the structureany)Follow the "3 attempts rule":
Never make more than 3 attempts without stopping to reassess.
This repository separates application code (
app/) from infrastructure (infra/).
Phase 1: Building the application locally using Docker Compose
Phase 2 (later): Set up cloud infrastructure with Terraform
See
docs/APP_INFRA_SEPARATION.md for details.
npm run check before committingnpm run check (it catches bugs)app/config/env.tsapp/repositories/db.tsapp/lib/*.tsapp/services/*.tsapp/repositories/*.tsapp/api/routes/*.tsapp/types/*.tsapp/api/routes/, register in app/api/server.tsapp/services/, import in routesapp/repositories/, import in servicesapp/types/, export from app/types/index.ts.sql file in migrations/LAYERED_ARCHITECTURE.mddocs/PHASE_1_LAYERED.mddocs/ARCHITECTURE_COMPARISON.mddocs/APP_INFRA_SEPARATION.mddocs/ULTRACITE_SETUP.md