{"id":45702374,"url":"https://github.com/dileeparanawake/littlesteps-ai","last_synced_at":"2026-02-24T23:05:56.280Z","repository":{"id":304667878,"uuid":"1018123906","full_name":"dileeparanawake/littlesteps-ai","owner":"dileeparanawake","description":"Full-stack LLM app with auth, prompt history, and Docker — for personalised parenting support","archived":false,"fork":false,"pushed_at":"2026-02-23T09:18:17.000Z","size":13672,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-23T18:07:11.336Z","etag":null,"topics":["ai","better-auth","chatapp","containerization","docker","drizzle","full-stack","google-oauth","llm","nextjs","openai","parenting","portfolio","postgres","react","react-query","software-engineering","typescript","vitest","zod"],"latest_commit_sha":null,"homepage":"https://littlesteps-ai.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dileeparanawake.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-11T16:39:20.000Z","updated_at":"2026-02-23T09:18:21.000Z","dependencies_parsed_at":"2025-07-14T17:14:47.208Z","dependency_job_id":"73a665d9-915f-405c-8842-305caed07f83","html_url":"https://github.com/dileeparanawake/littlesteps-ai","commit_stats":null,"previous_names":["dileeparanawake/littlesteps-ai"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/dileeparanawake/littlesteps-ai","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dileeparanawake%2Flittlesteps-ai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dileeparanawake%2Flittlesteps-ai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dileeparanawake%2Flittlesteps-ai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dileeparanawake%2Flittlesteps-ai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dileeparanawake","download_url":"https://codeload.github.com/dileeparanawake/littlesteps-ai/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dileeparanawake%2Flittlesteps-ai/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29804242,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-24T22:43:48.403Z","status":"ssl_error","status_checked_at":"2026-02-24T22:43:18.536Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","better-auth","chatapp","containerization","docker","drizzle","full-stack","google-oauth","llm","nextjs","openai","parenting","portfolio","postgres","react","react-query","software-engineering","typescript","vitest","zod"],"created_at":"2026-02-24T23:05:52.064Z","updated_at":"2026-02-24T23:05:56.274Z","avatar_url":"https://github.com/dileeparanawake.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# littlesteps-ai\n\n[![GitHub Repo](https://img.shields.io/badge/GitHub-littlesteps--ai-blue?logo=github)](https://github.com/dileeparanawake/littlesteps-ai)\n\nAI guidance for new parents - a full-stack, auth-gated LLM chat, built with **Next.js 15**, **PostgreSQL/Drizzle**, **React Query**, **Vitest**, **Docker**, **OpenAI**. Shipped in tagged minimum viable slices (MVS). [What’s an MVS? (Blog)](https://dileeparanawake.com/minimum-viable-slice).\n\n**Keywords:** Next.js, React, TypeScript, PostgreSQL, Drizzle ORM, Docker, React Query, Zod, BetterAuth.js, Google OAuth, OpenAI API, Vitest, Full-stack development, API design, Authentication, Authorization, Database design, Testing, Containerisation, REST APIs\n\n---\n\n- 🗂️ [Project Kanban](https://github.com/users/dileeparanawake/projects/4/views/1)\n- 🏷️ Release tags: [MVS1](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs1-complete) · [MVS2](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs2-complete) · [MVS3](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs3-complete) · [MVS4](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs4-complete) · [MVS5](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs5-complete)\n- 🔍 [Diff MVS4 → MVS5](https://github.com/dileeparanawake/littlesteps-ai/compare/mvs4-complete...mvs5-complete)\n\n## Demo\n\n![LittleSteps AI Demo MVS3 Complete](./docs/screenshots/littlesteps-screen-recording-2025-10-21-MVS3-complete.gif)\n\n### Skills demonstrated\n\n- Built a full-stack, auth-gated chat app using **Next.js**, **React**, **TypeScript**, and **React Query**.\n- Modelled and persisted data in **PostgreSQL** with **Drizzle ORM** and **SQL migrations**, containerised via **Docker Compose**.\n- Implemented **Google OAuth 2.0** authentication using **BetterAuth** with session-protected endpoints.\n- Validated and sanitised inputs using **Zod**; followed RESTful API conventions for predictable error handling.\n- Integrated **OpenAI's API** for prompt–response handling on the server.\n- Wrote **DB-backed tests** with **Vitest + Docker Postgres** to verify core actions and data integrity.\n- Deployed to **Fly.io** with **Neon** managed PostgreSQL, HTTPS, secrets management, and production-ready configuration.\n- Implemented **access controls** and **usage limits** around AI usage, using an adapter pattern for the AI provider and **GitHub Actions** for automated deployment.\n- Implemented **GDPR compliance** with a privacy policy page, automated inactive-user cleanup via **GitHub Actions OIDC**, and cascade deletes for data retention.\n\n## Features by Minimum Viable Slice\n\nEach slice represents a tagged, working release — from basic prompt handling to full auth-gated history.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eMVS5 — GDPR Compliance \u0026 Data Retention\u003c/b\u003e\u003c/summary\u003e\n\n**Goal:** Make LittleSteps GDPR-compliant with transparent privacy practices and automated data retention.\n\n**Key features**\n\n- **Privacy policy page** (`/privacy`) with footer and sign-in modal links.\n- **Automated inactive-user deletion** — monthly GitHub Actions workflow deletes accounts inactive for 90+ days.\n- **OIDC-secured admin endpoint** (`/api/admin/cleanup`) authenticated via GitHub Actions OIDC tokens.\n- **Verification table cleanup** decoupled from user deletion for independent lifecycle management.\n- Admin users excluded from auto-deletion; cascade deletes handle all related data.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eMVS4 — Production Deployment \u0026 Access Controls\u003c/b\u003e\u003c/summary\u003e\n\n**Goal:** Deploy LittleSteps to Fly.io as a production-ready application with safety guardrails and access controls.\n\n**Key features**\n\n- Deployed to **Fly.io** with **Neon** managed PostgreSQL, HTTPS, and secrets management.\n- **RBAC on routes** for initial release (admin-only access to chat APIs).\n- **Usage limits** enforced for non-admin users.\n- **AI provider adapter** with prompt caching (threadId-based).\n- **UI improvements**: safety banner, disclaimer, Open Graph image, mobile-responsive layout.\n- **Automated CD** via GitHub Actions for production deployment.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eMVS3 — Prompt History \u0026 Persistence\u003c/b\u003e\u003c/summary\u003e\n\n**Goal:** Persist chat threads and messages so logged-in users can revisit their prompt history.\n\n**Key features**\n\n- PostgreSQL + Drizzle schema for `thread` / `message` (UUIDs, ordered `sequence`).\n- Auth-gated APIs (`/api/chat`, `/api/threads`) enforcing ownership.\n- Rename \u0026 delete threads (≤60 chars, Zod validation, cascade delete).\n- React Query caching / invalidation keeps sidebar and thread lists synced.\n- DB-backed tests (Vitest + Docker Postgres) verify CRUD and ordering.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eMVS2 — User Authentication (Google OAuth)\u003c/b\u003e\u003c/summary\u003e\n\n**Goal:** Secure app access with Google OAuth via BetterAuth.js.\n\n**Key features**\n\n- Google sign-in modal using BetterAuth.js (HttpOnly sessions).\n- Auth-gated routes and session-aware UI state.\n- Early DB prototype used SQLite (migrated to Postgres in MVS3).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eMVS1 — Prompt Interface (No Auth)\u003c/b\u003e\u003c/summary\u003e\n\n**Goal:** Provide a simple OpenAI-powered prompt/response interface.\n\n**Key features**\n\n- Basic chat UI with secure API route to OpenAI.\n- Docker Compose setup for containerised local development.\n- Established initial minimum viable slice and project structure.\n\n\u003c/details\u003e\n\n## Tech Stack\n\n- **UI / App:** Next.js (App Router), React, TypeScript\n- **Data layer:** React Query (@tanstack/react-query)\n- **Database / ORM:** PostgreSQL (Docker, **Neon**) + Drizzle ORM (migrations)\n- **Auth:** BetterAuth.js (Google OAuth, HttpOnly sessions)\n- **Validation:** Zod\n- **LLM:** OpenAI API (SDK)\n- **Testing:** Vitest (DB-backed tests)\n- **Dev / Infra:** Node 20 (Volta), Docker \u0026 Docker Compose, Fly.io (production), **GitHub Actions (CD)**\n\n\u003e Note: SQLite was used in MVS2 only; MVS3 migrated to PostgreSQL.\n\n## Data Model\n\nA small, explicit schema that prioritises **ownership**, **deterministic ordering**, and **clean deletes**.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eData model (expand)\u003c/b\u003e\u003c/summary\u003e\n\n```txt\nUser (id TEXT PK, email UNIQUE, name, createdAt, updatedAt)\n ├─ Session (id TEXT PK, token UNIQUE, expiresAt, userId FK → User.id ON DELETE CASCADE)\n ├─ Account (id TEXT PK, accountId, providerId, userId FK → User.id ON DELETE CASCADE)\n └─ Thread (id UUID PK, userId FK → User.id ON DELETE CASCADE, title VARCHAR(60), createdAt, updatedAt)\n     └─ Message (id UUID PK, threadId FK → Thread.id ON DELETE CASCADE,\n                 sequence INT, role ENUM[system|user|assistant], content TEXT,\n                 createdAt, promptTokens?, completionTokens?, totalTokens?,\n                 UNIQUE(threadId, sequence))\n```\n\n\u003c/details\u003e\n\n### Key decisions (what \u0026 why)\n\n- **UUIDs for Thread/Message** → non-guessable, safe in URLs, simpler client routing.\n- **Deterministic ordering** → `UNIQUE(thread_id, sequence)` ensures stable message order without relying on timestamps.\n- **Ownership enforcement** → `thread.user_id` FK + API session checks (BetterAuth) guarantee users can only access their own threads.\n- **Clean deletes** → `ON DELETE CASCADE` on FKs automatically removes dependent messages.\n- **Tight title constraint** → `VARCHAR(60)` prevents UI overflow and keeps naming consistent.\n- **Role enum** (`system | user | assistant`) → validates message type at the DB layer.\n- **Token fields** (`promptTokens`, `completionTokens`, `totalTokens`) → reserved for future analytics and rate-limiting.\n\n### Migration \u0026 testing workflow\n\n- Schema changes are managed with **Drizzle migrations**.\n- Tests are **DB-backed** (Vitest + Docker Postgres): create thread → append messages → fetch ordered → assert content \u0026 order.\n\n## API quick reference\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eAPI reference (expand)\u003c/b\u003e\u003c/summary\u003e\n\nAuthentication required for all endpoints (BetterAuth session). Usage limits are currently applied to authenticated non-admin users only.\n\nPOST /api/chat?threadId=UUID (optional)\n\n- body: { \"prompt\": string }\n- 200: { \"threadID\": UUID }\n- 400 invalid body | 401 unauthenticated | 500 error\n- Behavior: appends user prompt, calls OpenAI, appends assistant reply; creates a thread if none provided.\n\nGET /api/chat?threadId=UUID\n\n- 200: Message[] ordered by sequence\n- 400 missing threadId | 401 unauthenticated | 403 not owner\n\nGET /api/threads\n\n- 200: Thread[] for the session user\n\nPATCH /api/threads\n\n- body: { \"threadId\": UUID, \"title\": string\u003c=60 }\n- 200: Thread | 400 invalid | 401 unauth | 403 forbidden\n\nDELETE /api/threads\n\n- body: { \"threadId\": UUID }\n- 200: { \"success\": true } | 401 unauth | 403 forbidden | 404 not found\n\n\u003c/details\u003e\n\n## Project structure\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eProject structure (expand)\u003c/b\u003e\u003c/summary\u003e\n\n```\n┌─ src/app/ (Next.js App Router)\n│  ├─ page.tsx (landing)\n│  ├─ chat/[threadId]/page.tsx (thread view)\n│  └─ api/\n│     ├─ auth/[...all]/route.ts (BetterAuth handlers)\n│     ├─ chat/route.ts (POST: send msg, GET: fetch msgs)\n│     └─ threads/route.ts (GET: list, PATCH: rename, DELETE)\n│\n├─ src/components/\n│  ├─ chat/\n│  │  ├─ ChatSidebar/ (thread list, rename, delete)\n│  │  └─ ChatThread/ (message list, input, submit)\n│  ├─ sign-in/ (Google OAuth modal)\n│  └─ layout/Header.tsx (nav, session UI)\n│\n└─ src/lib/\n   ├─ auth.ts (BetterAuth config)\n   ├─ chat/ (DB operations: create, read, update, delete)\n   └─ db/ (Drizzle schema, migrations)\n```\n\n**Data flow:** User → React components → API routes → Drizzle ORM → PostgreSQL\n\n\u003c/details\u003e\n\n## Architecture at a glance\n\n- **App:** Next.js App Router; server routes for APIs, client components for UI.\n- **Auth/Data:** BetterAuth.js + Drizzle ORM → Postgres (UUID, sequence ordering).\n- **Client state:** React Query cache; invalidation on rename/delete/post.\n- **Deployment:** Dockerised Next.js app on **Fly.io**, connected to **Neon** managed PostgreSQL, deployed via **GitHub Actions**.\n\n## Tests\n\n```bash\npnpm test\n```\n\nDB-backed (Vitest + Docker Postgres): create/read/update/delete flows for threads/messages.\n\n## Local Development\n\nSee full guide: [`public/docs/local-dev-guide.md`](./docs/local-dev-guide.md)\n\nQuick start:\n\n```bash\ndocker compose up -d\npnpm migrate\n```\n\n## Releases\n\n- 2026-02-24 — MVS5: GDPR compliance \u0026 data retention — [Tag](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs5-complete)\n- 2025-12-26 — MVS4: Production deployment \u0026 access controls — [Tag](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs4-complete)\n- 2025-10-21 — MVS3: Prompt history \u0026 persistence — [Tag](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs3-complete)\n- 2025-07-27 — MVS2: User Authentication — [Tag](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs2-complete)\n- 2025-07-20 — MVS1: Prompt Interface — [Tag](https://github.com/dileeparanawake/littlesteps-ai/releases/tag/mvs1-complete)\n\n## License\n\nThis project is licensed under the [MIT License](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdileeparanawake%2Flittlesteps-ai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdileeparanawake%2Flittlesteps-ai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdileeparanawake%2Flittlesteps-ai/lists"}