Skip to content

Commit 154ba38

Browse files
feat: Implement offline API request queuing for mobile and overhaul project documentation.
1 parent 314789d commit 154ba38

4 files changed

Lines changed: 172 additions & 12 deletions

File tree

README.md

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,154 @@
1-
# FairShare Monorepo
1+
# 💸 FairShare
22

3-
Production-grade scaffold for Expo mobile + NestJS backend + Next.js web placeholder.
3+
**A Production-Grade Monorepo for Collaborative Expense Sharing.**
44

5+
FairShare is a high-performance, full-stack solution designed for seamless expense tracking and settlement. Built with a modern TypeScript-first architecture, it provides a unified experience across mobile, web, and backend services.
6+
7+
---
8+
9+
## 🚀 Overview
10+
11+
FairShare simplifies communal living and shared adventures. Whether you're splitting rent with roommates or tracking costs on a group trip, FairShare provides the tools to handle complex splits, automate settlements via UPI, and maintain real-time visibility into balances.
12+
13+
### Key Highlights
14+
15+
- **Cross-Platform**: Expo-powered mobile app and Next.js web dashboard.
16+
- **Robust Backend**: Scalable NestJS microservice with Prisma and Supabase.
17+
- **Financial Integrity**: High-precision BigInt arithmetic for accurate penny-perfect splits.
18+
- **Production Ready**: Fully Dockerized, Terraform-orchestrated, and CI/CD-integrated.
19+
20+
---
21+
22+
## 🛠 Tech Stack
23+
24+
### Backend
25+
26+
- **Framework**: NestJS (TypeScript strict)
27+
- **Database**: PostgreSQL (Supabase) + Prisma ORM
28+
- **Caching**: Redis (BullMQ for async jobs)
29+
- **Storage**: AWS S3 (Receipts & Assets)
30+
- **Security**: JWT Rotation, Google OAuth, Helmet, CSRF Protection
31+
- **Observability**: Prometheus metrics + Sentry + OpenTelemetry
32+
33+
### Mobile
34+
35+
- **Framework**: Expo Router (React Native)
36+
- **Styling**: React Native Paper + Reanimated animations
37+
- **State/Networking**: Axios + Socket.io + TanStack Query (planned)
38+
- **Native Hooks**: Haptic feedback, Lottie animations, UPI Deep-linking
39+
40+
### Web
41+
42+
- **Framework**: Next.js
43+
- **Styling**: TailwindCSS + Framer Motion
44+
- **SEO**: Dynamic metadata & SSR optimized
45+
46+
---
47+
48+
## 📁 Project Structure
49+
50+
This project uses **Turborepo** and **pnpm** for workspace management:
51+
52+
```text
53+
FairShare/
54+
├── apps/
55+
│ ├── backend/ # NestJS API Service
56+
│ ├── mobile/ # Expo / React Native App
57+
│ └── web/ # Next.js Marketing & Dashboard
58+
├── packages/
59+
│ └── shared-types/ # Shared TS interfaces & Zod schemas
60+
├── infra/ # Terraform modules (AWS ECS, RDS, S3)
61+
└── scripts/ # Automation & seeding utilities
62+
```
63+
64+
---
65+
66+
## 🚦 Getting Started
67+
68+
### Prerequisites
69+
70+
- [Node.js](https://nodejs.org/) (v18+)
71+
- [pnpm](https://pnpm.io/) (v8+)
72+
- [Docker](https://www.docker.com/) (for local services)
73+
74+
### Installation
75+
76+
1. **Clone the repository**:
77+
78+
```bash
79+
git clone https://github.com/Arun-kushwaha007/FairShare.git
80+
cd FairShare
81+
```
82+
83+
2. **Install dependencies**:
84+
85+
```bash
86+
pnpm install
87+
```
88+
89+
3. **Environment Setup**:
90+
91+
Copy `.env.example` to `.env` in the root and within `apps/backend/`.
92+
93+
```bash
94+
cp .env.example .env
95+
```
96+
97+
> [!IMPORTANT]
98+
> The backend requires several mandatory environment variables to bootstrap successfully. Ensure the following are set in `apps/backend/.env`:
99+
> - `SUPABASE_DATABASE_URL`: Your PostgreSQL connection string.
100+
> - `JWT_SECRET` & `JWT_REFRESH_SECRET`: Secure strings for token signing.
101+
> - `STRIPE_SECRET_KEY`: Required for the payments module (starts with `sk_test_`).
102+
> - `STRIPE_WEBHOOK_SECRET`: Required for processing payment events (starts with `whsec_`).
103+
> - `GOOGLE_CLIENT_ID` & `SECRET`: For OAuth integration.
104+
105+
4. **Start Development Services**:
106+
107+
```bash
108+
# Start DB, Redis via Docker
109+
docker-compose up -d
110+
111+
# Run all apps in dev mode (Turbo)
112+
pnpm dev
113+
```
114+
115+
### 💻 Developing Locally
116+
117+
You can also start specific services individually using the following commands from the root:
118+
119+
| Service | Command | Description |
120+
| :--- | :--- | :--- |
121+
| **All Service** | `pnpm dev` | Starts Backend, Mobile, and Web in parallel via Turbo. |
122+
| **Backend** | `pnpm dev:backend` | Starts the NestJS API with hot reload. |
123+
| **Mobile** | `pnpm dev:mobile` | Starts the Expo development server (Expo Go). |
124+
| **Web** | `pnpm dev:web` | Starts the Next.js development server. |
125+
| **Seeding** | `pnpm seed` | Populates the database with initial development data. |
126+
| **Building Mobile** | `pnpm mobile:build` | Triggers EAS build for production AAB/IPA. |
127+
128+
---
129+
130+
## 🔋 Core Features
131+
132+
- **Advanced Split Logic**: Supports equal, exact, and percentage-based splits.
133+
- **Fast Settlements**: Greedy simplification algorithm to minimize total payments.
134+
- **Real-time Activity**: Live updates via WebSockets for group actions.
135+
- **Offline Resilience**: Mobile-first architecture with offline queuing for expense entry.
136+
- **Push Notifications**: Intelligent reminders for pending settlements via Expo.
137+
138+
---
139+
140+
## 🚢 Infrastructure & Deployment
141+
142+
- **CI/CD**: GitHub Actions for automated type-checking, linting, and Playwright E2E testing.
143+
- **Cloud Hosting**: AWS ECS (Fargate) for backend, Vercel/Netlify for web, and EAS for mobile.
144+
- **IaC**: Terraform-managed VPC, ECS Cluster, RDS, and S3 buckets.
145+
146+
---
147+
148+
## 📄 License & Documentation
149+
150+
Refer to [doc.md](doc.md) for detailed technical architecture and production launch notes.
151+
152+
---
153+
154+
*Developed with ❤️ by the FairShare Team.*

apps/backend/src/settlements/settlements.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { Module } from '@nestjs/common';
1+
import { forwardRef, Module } from '@nestjs/common';
22
import { BalancesModule } from '../balances/balances.module';
33
import { NotificationsModule } from '../notifications/notifications.module';
44
import { SettlementsController } from './settlements.controller';
55
import { SettlementsService } from './settlements.service';
66

77
@Module({
8-
imports: [BalancesModule, NotificationsModule],
8+
imports: [BalancesModule, forwardRef(() => NotificationsModule)],
99
controllers: [SettlementsController],
1010
providers: [SettlementsService],
1111
exports: [SettlementsService],

apps/mobile/app/services/api.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ export const api = axios.create({
4343
timeout: 15000,
4444
});
4545

46+
offlineQueue.setRequestExecutor(async (request) => {
47+
await api.request({
48+
method: request.method,
49+
url: request.url,
50+
data: request.data,
51+
headers: { 'x-offline-retry': '1' },
52+
});
53+
});
54+
4655
if (__DEV__) {
4756
// Helps debug real-device failures where localhost is unreachable.
4857
console.log('[api] baseURL:', baseURL);

apps/mobile/app/utils/offlineQueue.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import NetInfo from '@react-native-community/netinfo';
22
import * as SecureStore from 'expo-secure-store';
3-
import { api } from '../services/api';
43

54
type OfflineRequest = {
65
id: string;
@@ -9,11 +8,18 @@ type OfflineRequest = {
98
data: unknown;
109
};
1110

11+
type OfflineRequestExecutor = (request: OfflineRequest) => Promise<void>;
12+
1213
const STORAGE_KEY = 'fairshare_offline_queue';
1314

1415
class OfflineQueue {
1516
private initialized = false;
1617
private flushing = false;
18+
private requestExecutor: OfflineRequestExecutor | null = null;
19+
20+
setRequestExecutor(executor: OfflineRequestExecutor): void {
21+
this.requestExecutor = executor;
22+
}
1723

1824
async enqueue(req: OfflineRequest): Promise<void> {
1925
const queue = await this.readQueue();
@@ -47,19 +53,14 @@ class OfflineQueue {
4753

4854
try {
4955
const queue = await this.readQueue();
50-
if (queue.length === 0) {
56+
if (queue.length === 0 || !this.requestExecutor) {
5157
return;
5258
}
5359

5460
const remaining: OfflineRequest[] = [];
5561
for (const item of queue) {
5662
try {
57-
await api.request({
58-
method: item.method,
59-
url: item.url,
60-
data: item.data,
61-
headers: { 'x-offline-retry': '1' },
62-
});
63+
await this.requestExecutor(item);
6364
} catch {
6465
remaining.push(item);
6566
}

0 commit comments

Comments
 (0)