Dual-mode whistleblowing platform:
Frontend:
├── Web: React 19, TailwindCSS, TypeScript
├── State: React Query (TanStack),
└── Mobile: Expo / React Native
Backend:
├── Runtime: Bun
├── Framework: Elysia.js
├── Language: TypeScript
└── Jobs: Bull Queue (Redis-backed)
Database:
├── Primary: PostgreSQL
├── Cache: Redis 7 (for sessions, rate limiting, job queue)
└── Search: PostgreSQL Full-Text Search (tsvector)
Blockchain:
├── Network: Hedera
├── SDK: @hashgraph/sdk
└── Service: HCS (Consensus Service)
File Storage:
├── Anonymous: IPFS via Web3.Storage (free)
└── Identified: Supabase Storage (free tier)
Authentication:
├── JWT (access + refresh tokens)
├── Password: bcrypt (cost: 12)
└── Admin: Same as regular users but with admin role
Background Jobs:
├── Queue: Bull (Redis)
└── Scheduler: node-cron
Redis Usage:
├── Session storage (JWT refresh tokens)
├── Rate limiting counters
├── Bull queue for background jobs (HCS indexer ...)
project-root/
├── frontend/
│ ├── app/
│ │ ├── (auth)/
│ │ │ ├── login/
│ │ │ └── signup/
│ │ ├── dashboard/
│ │ │ ├── submit/ # Mode selection + forms
│ │ │ ├── my-complaints/
│ │ │ │ ├── anonymous/
│ │ │ │ └── identified/
│ │ │ └── profile/
│ │ ├── admin/
│ │ │ ├── anonymous/
│ │ │ ├── identified/
│ │ ├── public/
│ │ │ ├── browse/ # Public complaint browsing
│ │ │ ├── complaint/[hash]/
│ │ └── layout.tsx
│ ├── components/
│ │ ├── ui/
│ │ ├── forms/
│ │ └── layouts/
│ └── lib/
│ ├── api.ts
│ └── utils.ts
│
├── backend/
│ ├── src/
│ │ ├── routes/
│ │ │ ├── auth.routes.ts
│ │ │ ├── complaint.routes.ts
│ │ │ ├── admin.routes.ts
│ │ │ └── public.routes.ts
│ │ ├── controllers/
│ │ ├── services/
│ │ │ ├── hedera.service.ts
│ │ │ ├── ipfs.service.ts
│ │ │ └── indexer.service.ts
│ │ ├── middleware/
│ │ │ ├── auth.middleware.ts
│ │ │ ├── validation.middleware.ts
│ │ │ └── rateLimit.middleware.ts
│ │ ├── jobs/
│ │ │ ├── hcs-indexer.job.ts
│ │ └── server.ts
│ ├── prisma/
│ │ └── schema.prisma
│ └── package.json
│
└── README.md
User Registration:
User Login:
Profile Management:
Password Recovery: