{"id":51563894,"url":"https://github.com/arcsymer/restos-portal","last_synced_at":"2026-07-10T13:02:06.449Z","repository":{"id":369787225,"uuid":"1291576198","full_name":"arcsymer/restos-portal","owner":"arcsymer","description":"Loyalty \u0026 rewards API for RestOS: NestJS 11, JWT auth+roles, points ledger with tiers, Prisma (SQLite/Postgres), throttler, OpenAPI","archived":false,"fork":false,"pushed_at":"2026-07-06T23:53:55.000Z","size":152,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-07T01:11:23.226Z","etag":null,"topics":["authentication","backend","jwt","nestjs","openapi","portfolio","prisma","react","rest-api","typescript"],"latest_commit_sha":null,"homepage":null,"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/arcsymer.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":"2026-07-06T23:31:15.000Z","updated_at":"2026-07-06T23:54:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/arcsymer/restos-portal","commit_stats":null,"previous_names":["arcsymer/restos-portal"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/arcsymer/restos-portal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arcsymer%2Frestos-portal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arcsymer%2Frestos-portal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arcsymer%2Frestos-portal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arcsymer%2Frestos-portal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arcsymer","download_url":"https://codeload.github.com/arcsymer/restos-portal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arcsymer%2Frestos-portal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35331955,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-10T02:00:06.465Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["authentication","backend","jwt","nestjs","openapi","portfolio","prisma","react","rest-api","typescript"],"created_at":"2026-07-10T13:02:00.788Z","updated_at":"2026-07-10T13:02:06.444Z","avatar_url":"https://github.com/arcsymer.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# restos-portal\n\nCustomer loyalty \u0026 rewards backend for the RestOS ecosystem: JWT auth with roles, a points\nledger with tiers, and reward redemption — NestJS 11 + Prisma, running on zero-setup SQLite by\ndefault (swap `DATABASE_URL` for Postgres).\n\n![Node](https://img.shields.io/badge/Node-22+-339933?logo=node.js\u0026logoColor=white)\n![NestJS](https://img.shields.io/badge/NestJS-11-E0234E?logo=nestjs\u0026logoColor=white)\n![Prisma](https://img.shields.io/badge/Prisma-6-2D3748?logo=prisma\u0026logoColor=white)\n[![ci](https://github.com/arcsymer/restos-portal/actions/workflows/ci.yml/badge.svg)](https://github.com/arcsymer/restos-portal/actions/workflows/ci.yml)\n![License: MIT](https://img.shields.io/badge/license-MIT-green)\n\nDemo evidence: [docs/demo-transcript.md](docs/demo-transcript.md) — a real request/response\nsession (register → earn → redeem → 403/429).\n\n## Quickstart\n\nPrerequisites: **Node 22+** and **pnpm 9+** — nothing else (SQLite file DB, no accounts, no paid\nservices).\n\n```sh\ngit clone https://github.com/arcsymer/restos-portal \u0026\u0026 cd restos-portal\ncp .env.example .env   # Windows cmd.exe: copy .env.example .env\npnpm install           # postinstall runs `prisma generate`\npnpm run db:setup      # applies migrations + seeds the rewards catalog \u0026 demo users (SQLite)\npnpm start\n```\n\n- Swagger UI: \u003chttp://localhost:3000/docs\u003e · health: \u003chttp://localhost:3000/health\u003e\n- Seeded demo logins (password `password123`): `anna@example.com` (customer, 150 pts),\n  `staff@example.com` (staff).\n\nExample: log in, then read your loyalty account.\n\n```sh\ncurl -s -X POST localhost:3000/auth/login -H \"Content-Type: application/json\" \\\n  -d \"{\\\"email\\\":\\\"anna@example.com\\\",\\\"password\\\":\\\"password123\\\"}\"\n# → { \"accessToken\": \"...\", \"refreshToken\": \"...\" }\ncurl -s localhost:3000/me/account -H \"Authorization: Bearer \u003caccessToken\u003e\"\n# → { \"balance\": 150, \"lifetimePoints\": 150, \"tier\": \"nowicjusz\" }\n```\n\n## Web dashboard (`web/`)\n\nA small React 19 + Vite front for customers — sign in, see balance/tier/history, redeem rewards.\nIt proxies to the API on `:3000`.\n\n```sh\ncd web \u0026\u0026 pnpm install \u0026\u0026 pnpm dev    # http://localhost:5173 (API must be running)\n```\n\n![Loyalty dashboard: balance, tier, redeemable rewards, and points history](docs/dashboard.png)\n\n## Architecture\n\n```mermaid\nflowchart LR\n    C[client / Swagger] --\u003e|JWT| API[NestJS /api]\n    API --\u003e AUTH[auth: register/login/refresh\\nbcrypt + access/refresh JWT]\n    API --\u003e LOY[loyalty: account, ledger, rewards,\\nredeem, staff earn]\n    subgraph guards[global guards]\n      THR[Throttler] --\u003e JWTG[JwtAuthGuard] --\u003e ROLE[RolesGuard]\n    end\n    API --- guards\n    LOY --\u003e DB[(Prisma → SQLite default\\n| Postgres via DATABASE_URL)]\n```\n\nGlobal cross-cutting: `ValidationPipe` (whitelist DTOs), `ThrottlerGuard` (tight on `/auth/login`),\n`JwtAuthGuard` (`@Public()` opt-out), `RolesGuard` (`@Roles('staff')`), a cache on the rewards list.\n\n## Features\n\n1. **JWT auth** — register / login / refresh, bcrypt hashing, access + refresh tokens.\n2. **Roles** — `customer` / `staff` via a guard; staff-only endpoints (e.g. crediting points).\n3. **Points ledger** — append-only earn/redeem entries; balance is their sum; tiers\n   (`nowicjusz` → `staly` → `klub`) derived from lifetime points.\n4. **Redemption** — redeem a reward from the seeded catalog if the balance covers it (else 400).\n5. **Rate limiting + caching** — global throttler with a tight per-route limit on login;\n   cache-manager on the rewards catalog.\n6. **OpenAPI + validation** — Swagger UI, class-validator DTOs, consistent error codes.\n7. **Prisma persistence** — migrations + seed; SQLite default, Postgres via `DATABASE_URL`.\n\n## GraphQL gateway\n\nAlongside the REST API, the same loyalty operations are exposed over **GraphQL** at `/graphql`\n(code-first schema, Apollo). It reuses the exact same `LoyaltyService` and the same global JWT +\nrole guards — the guards are execution-context aware, so a customer still can't call the\nstaff-only `earn` mutation. Example (with a `Bearer` token):\n\n```graphql\nquery { myAccount { balance tier } rewards { code costPoints } }\nmutation { redeem(rewardCode: \"FREE_SOUP\") { balance tier } }\nmutation { earn(email: \"anna@example.com\", points: 40, reason: \"order\") { balance } }  # staff only\n```\n\nAuth stays on REST (`/auth/login`); the GraphQL layer is the read/redeem gateway. Covered by\n`test/graphql.e2e-spec.ts` (queries, mutations, unauthenticated rejection, and the 403 path).\n\n## Testing \u0026 CI\n\n```sh\npnpm run test          # unit (jest)\npnpm run test:e2e      # supertest e2e — full flow incl. 401/403/400/409/429\npnpm run lint:ci\n```\n\nCI runs migrate + seed on a fresh SQLite DB, then build + lint + unit + e2e + gitleaks.\n\n## Product notes\n\nSee [PRODUCT.md](PRODUCT.md) — user stories, success metrics, and an experiment plan.\n\n## Limitations\n\n- No email verification or password reset; refresh tokens aren't rotated/revoked (stateless JWT).\n- Points are credited via a staff/system endpoint — there's no live order→points webhook from\n  restos-core yet (a cross-service v2 idea).\n- SQLite by default is single-file; for real concurrency use the Postgres `DATABASE_URL`.\n- All data is synthetic; demo credentials are public and for local use only.\n\n## License \u0026 attribution\n\nMIT — see [LICENSE](LICENSE). Part of the [RestOS](https://github.com/arcsymer) portfolio.\n\nBuilt end-to-end with an agentic workflow (Claude Code), orchestrated, reviewed, and directed by me.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farcsymer%2Frestos-portal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farcsymer%2Frestos-portal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farcsymer%2Frestos-portal/lists"}