Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copilot Instructions

This is an E-commerce microservices architecture built with TypeScript in a Turborepo monorepo. Understanding the service patterns and communication flows is critical for effective development.

## Architecture Overview

### Microservices Structure
- **auth-service** (port 8003) - Clerk authentication, user management
- **product-service** (port 8000) - Product CRUD, Prisma/PostgreSQL
- **order-service** (port 8001) - Order management, MongoDB, Fastify
- **payment-service** (port 8002) - Stripe payments, Hono framework
- **email-service** - Kafka consumer for notifications

### Frontend Applications
- **client** (port 3002) - Customer Next.js app with Stripe integration
- **admin** (port 3003) - Admin dashboard for management

## Key Development Patterns

### Authentication Flow
All services use Clerk for auth. Services validate tokens via `@clerk/express` middleware:
```typescript
// Pattern: shouldBeUser/shouldBeAdmin middleware
import { getAuth } from "@clerk/express";
const auth = getAuth(req);
req.userId = auth.userId;
```

### Service Communication
Services communicate via Kafka events using `@repo/kafka` package:
```typescript
// Pattern: Event production
producer.send("user.created", { value: { email, username } });

// Pattern: Event consumption
consumer.subscribe([{
topicName: "order.created",
topicHandler: async (message) => { /* handle */ }
}]);
```

### Database Patterns
- **product-service**: Prisma client with **Neon PostgreSQL** (serverless)
- **order-service**: Mongoose with **MongoDB Atlas Serverless**
- **Shared types**: `@repo/types` for consistent data structures
- **Connection handling**: Optimized for serverless with connection pooling

### Serverless Database Configuration
```typescript
// Neon PostgreSQL - uses connection pooling automatically
DATABASE_URL="postgresql://user:pass@ep-xxx.neon.tech/db?pgbouncer=true"
DIRECT_URL="postgresql://user:pass@ep-xxx.neon.tech/db" // for migrations

// MongoDB Atlas Serverless - optimized connection options
MONGO_URL="mongodb+srv://user:pass@cluster.mongodb.net/db?retryWrites=true&w=majority"
```

### Development Commands

```bash
# Start all services in development
pnpm dev

# Start specific service
turbo dev --filter=product-service

# Database operations (from workspace root)
pnpm --filter=@repo/product-db db:generate
pnpm --filter=@repo/product-db db:migrate

# Start Kafka cluster
cd packages/kafka && docker-compose up
```

### Service Ports & URLs
- product-service: 8000
- order-service: 8001
- payment-service: 8002
- auth-service: 8003
- client: 3002
- admin: 3003

### Essential Environment Variables
- `DATABASE_URL` - Neon PostgreSQL (with pgbouncer connection pooling)
- `DIRECT_URL` - Neon PostgreSQL direct connection (for migrations)
- `MONGO_URL` - MongoDB Atlas Serverless
- `CLERK_SECRET_KEY` - Authentication
- `STRIPE_SECRET_KEY` - Payments
- `NEXT_PUBLIC_*_SERVICE_URL` - Service endpoints for frontends

### Framework-Specific Notes
- **auth/product-service**: Express.js with middleware patterns
- **order-service**: Fastify with plugin registration
- **payment-service**: Hono framework with different syntax
- **Frontend apps**: Next.js 15 with App Router, server components for data fetching

When working on services, always check the corresponding package.json for service-specific scripts and the utils/kafka.js file for event communication setup.
85 changes: 85 additions & 0 deletions DATABASE_CONFIG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Environment Variables for Serverless Databases

This project uses serverless databases for better scalability and cost efficiency.

## PostgreSQL (Neon)

### Required Environment Variables:
```bash
# Neon PostgreSQL Connection (with connection pooling)
DATABASE_URL="postgresql://username:password@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require&pgbouncer=true&connect_timeout=15"

# Direct URL for migrations (without connection pooling)
DIRECT_URL="postgresql://username:password@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require"
```

### Neon Setup Steps:
1. Create a Neon project at https://neon.tech
2. Copy the connection string from Neon dashboard
3. Use the pooled connection for `DATABASE_URL`
4. Use the direct connection for `DIRECT_URL`

## MongoDB (Atlas Serverless)

### Required Environment Variables:
```bash
# MongoDB Atlas Serverless Connection
MONGO_URL="mongodb+srv://username:password@cluster.xxxxx.mongodb.net/dbname?retryWrites=true&w=majority"
```

### MongoDB Atlas Serverless Setup Steps:
1. Create a MongoDB Atlas account
2. Create a new cluster and select "Serverless"
3. Set up database user and network access
4. Copy the connection string and replace credentials

## Development vs Production

### Development (.env.local):
```bash
# Development databases (can use smaller instances)
DATABASE_URL="postgresql://dev_user:dev_pass@ep-dev.neon.tech/ecom_dev?sslmode=require&pgbouncer=true"
DIRECT_URL="postgresql://dev_user:dev_pass@ep-dev.neon.tech/ecom_dev?sslmode=require"
MONGO_URL="mongodb+srv://dev_user:dev_pass@dev-cluster.mongodb.net/ecom_dev?retryWrites=true&w=majority"
```

### Production (.env.production):
```bash
# Production databases (optimized for performance)
DATABASE_URL="postgresql://prod_user:prod_pass@ep-prod.neon.tech/ecom_prod?sslmode=require&pgbouncer=true&connect_timeout=15"
DIRECT_URL="postgresql://prod_user:prod_pass@ep-prod.neon.tech/ecom_prod?sslmode=require"
MONGO_URL="mongodb+srv://prod_user:prod_pass@prod-cluster.mongodb.net/ecom_prod?retryWrites=true&w=majority"
```

## Database Operations

### PostgreSQL (Prisma) Commands:
```bash
# Generate Prisma client
pnpm --filter=@repo/product-db db:generate

# Run migrations (uses DIRECT_URL)
pnpm --filter=@repo/product-db db:migrate

# Deploy to production
pnpm --filter=@repo/product-db db:deploy
```

### MongoDB Operations:
- MongoDB Atlas Serverless automatically handles scaling
- No manual migration commands needed
- Schema changes are handled at application level via Mongoose models

## Benefits of Serverless Setup

### Neon PostgreSQL:
- **Auto-scaling**: Scales to zero when not in use
- **Branching**: Create database branches for development
- **Connection pooling**: Built-in PgBouncer for better performance
- **Point-in-time recovery**: Automatic backups

### MongoDB Atlas Serverless:
- **Pay-per-use**: Only pay for operations and storage used
- **Auto-scaling**: Automatically scales based on demand
- **Global clusters**: Low latency worldwide
- **Built-in security**: Encryption and network isolation
69 changes: 44 additions & 25 deletions apps/admin/src/app/(dashboard)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,52 @@ import { auth } from "@clerk/nextjs/server";
const Homepage = async () => {
const { getToken } = await auth();
const token = await getToken();
const orderChartData = fetch(
`${process.env.NEXT_PUBLIC_ORDER_SERVICE_URL}/order-chart`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);

let orderChartData = [];
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_ORDER_SERVICE_URL}/order-chart`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await res.json();
orderChartData = JSON.parse(JSON.stringify(data));
} catch (error) {
console.error('Failed to fetch order chart data:', error);
orderChartData = [];
}

return (
<div className="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-4 gap-4">
<div className="bg-primary-foreground p-4 rounded-lg lg:col-span-2 xl:col-span-1 2xl:col-span-2">
<AppBarChart dataPromise={orderChartData} />
</div>
<div className="bg-primary-foreground p-4 rounded-lg">
<CardList title="Latest Transactions" />
</div>
<div className="bg-primary-foreground p-4 rounded-lg">
<AppPieChart />
<div className="space-y-6">
{/* Page Header */}
<div className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white p-6 rounded-lg">
<h1 className="text-3xl font-bold mb-2">Neuraltale Admin Dashboard</h1>
<p className="text-blue-100">Manage your premium tech products and track sales performance</p>
</div>
<div className="bg-primary-foreground p-4 rounded-lg">
<TodoList />
</div>
<div className="bg-primary-foreground p-4 rounded-lg lg:col-span-2 xl:col-span-1 2xl:col-span-2">
<AppAreaChart />
</div>
<div className="bg-primary-foreground p-4 rounded-lg">
<CardList title="Popular Products" />

{/* Main Dashboard Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-4 gap-4">
<div className="bg-primary-foreground p-4 rounded-lg lg:col-span-2 xl:col-span-1 2xl:col-span-2">
<AppBarChart data={orderChartData} />
</div>
<div className="bg-primary-foreground p-4 rounded-lg">
<CardList title="Latest Tech Orders" />
</div>
<div className="bg-primary-foreground p-4 rounded-lg">
<AppPieChart />
</div>
<div className="bg-primary-foreground p-4 rounded-lg">
<TodoList />
</div>
<div className="bg-primary-foreground p-4 rounded-lg lg:col-span-2 xl:col-span-1 2xl:col-span-2">
<AppAreaChart />
</div>
<div className="bg-primary-foreground p-4 rounded-lg">
<CardList title="Trending Tech Products" />
</div>
</div>
</div>
);
Expand Down
5 changes: 3 additions & 2 deletions apps/admin/src/app/(dashboard)/products/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ const ProductPage = async () => {
const data = await getData();
return (
<div className="">
<div className="mb-8 px-4 py-2 bg-secondary rounded-md">
<h1 className="font-semibold">All Products</h1>
<div className="mb-8 px-6 py-4 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-lg">
<h1 className="text-2xl font-bold text-gray-900 mb-2">Tech Products Management</h1>
<p className="text-gray-600">Manage your premium tech product catalog including smartphones, laptops, audio gear, and accessories.</p>
</div>
<DataTable columns={columns} data={data} />
</div>
Expand Down
9 changes: 6 additions & 3 deletions apps/admin/src/app/(dashboard)/users/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ export function DataTable<TData, TValue>({
const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState({});

// Ensure data is always an array
const safeData = Array.isArray(data) ? data : [];

const table = useReactTable({
data,
data: safeData,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
Expand All @@ -61,10 +64,10 @@ export function DataTable<TData, TValue>({
const token = await getToken();
const selectedRows = table.getSelectedRowModel().rows;

Promise.all(
await Promise.all(
selectedRows.map(async (row) => {
const userId = (row.original as User).id;
const res = await fetch(
await fetch(
`${process.env.NEXT_PUBLIC_AUTH_SERVICE_URL}/users/${userId}`,
{
method: "DELETE",
Expand Down
15 changes: 13 additions & 2 deletions apps/admin/src/app/(dashboard)/users/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,21 @@ const getData = async (): Promise<{ data: User[]; totalCount: number }> => {
},
}
);

if (!res.ok) {
console.error(`Failed to fetch users: ${res.status} ${res.statusText}`);
return { data: [], totalCount: 0 };
}

const data = await res.json();
return data;

// Ensure data has the expected structure
return {
data: Array.isArray(data.data) ? data.data : [],
totalCount: typeof data.totalCount === 'number' ? data.totalCount : 0
};
} catch (err) {
console.log(err);
console.error("Error fetching users:", err);
return { data: [], totalCount: 0 };
}
};
Expand Down
Loading