Initial commit: Sase.tr VIN Sorgulama Platformu
Features: - Next.js 16.1.3 frontend with Turbopack - NestJS API with Prisma ORM - EMEX VIN scraper integration - Turkish translations for automotive parts - JWT authentication with refresh tokens - PM2 production deployment Tech Stack: - Frontend: Next.js 16.1, React 19, TailwindCSS - Backend: NestJS, Prisma, MySQL - Scraping: Puppeteer Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
681
docs/API_REFERENCE.md
Normal file
681
docs/API_REFERENCE.md
Normal file
@@ -0,0 +1,681 @@
|
||||
# Sase.tr API Reference
|
||||
|
||||
Base URL: `https://sase.tr/api`
|
||||
|
||||
## Response Format
|
||||
|
||||
All responses follow this structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { ... },
|
||||
"timestamp": "2026-01-16T12:00:00.000Z",
|
||||
"path": "/api/endpoint"
|
||||
}
|
||||
```
|
||||
|
||||
Error responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "ERROR_CODE",
|
||||
"message": "Human readable message"
|
||||
},
|
||||
"timestamp": "2026-01-16T12:00:00.000Z",
|
||||
"path": "/api/endpoint"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### POST `/auth/register`
|
||||
|
||||
Create a new user account.
|
||||
|
||||
**Rate Limit:** 3 requests/minute
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "securePassword123",
|
||||
"name": "John Doe"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"user": {
|
||||
"id": "clk1234567890",
|
||||
"email": "user@example.com",
|
||||
"name": "John Doe",
|
||||
"role": "USER",
|
||||
"isActive": true
|
||||
},
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
|
||||
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST `/auth/login`
|
||||
|
||||
Authenticate user and receive tokens.
|
||||
|
||||
**Rate Limit:** 5 requests/minute
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "securePassword123"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"user": {
|
||||
"id": "clk1234567890",
|
||||
"email": "user@example.com",
|
||||
"name": "John Doe",
|
||||
"role": "USER"
|
||||
},
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
|
||||
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- `401 Unauthorized` - Invalid credentials
|
||||
- `429 Too Many Requests` - Rate limit exceeded
|
||||
|
||||
---
|
||||
|
||||
### POST `/auth/refresh`
|
||||
|
||||
Refresh access token using refresh token.
|
||||
|
||||
**Rate Limit:** 10 requests/minute
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
|
||||
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST `/auth/logout`
|
||||
|
||||
Logout current user.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"message": "Basariyla cikis yapildi"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST `/auth/forgot-password`
|
||||
|
||||
Request password reset email.
|
||||
|
||||
**Rate Limit:** 3 requests/minute
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"message": "Eger email kayitliysa, sifre sifirlama linki gonderildi"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST `/auth/reset-password`
|
||||
|
||||
Reset password with token.
|
||||
|
||||
**Rate Limit:** 5 requests/minute
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"token": "reset-token-uuid",
|
||||
"password": "newSecurePassword123"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"message": "Sifre basariyla degistirildi"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/auth/me`
|
||||
|
||||
Get current user profile with subscription and brand info.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "clk1234567890",
|
||||
"email": "user@example.com",
|
||||
"name": "John Doe",
|
||||
"role": "USER",
|
||||
"subscription": {
|
||||
"id": "sub_123",
|
||||
"status": "ACTIVE",
|
||||
"plan": {
|
||||
"name": "Premium",
|
||||
"brandLimit": 5,
|
||||
"hasFullAccess": false
|
||||
},
|
||||
"currentPeriodEnd": "2026-02-16T00:00:00.000Z"
|
||||
},
|
||||
"selectedBrands": [
|
||||
{ "brand": { "code": "BMW", "name": "BMW" } },
|
||||
{ "brand": { "code": "AUDI", "name": "Audi" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vehicles
|
||||
|
||||
### POST `/vehicles/decode`
|
||||
|
||||
Decode VIN and return vehicle information.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token + Brand Access)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"vin": "WVWZZZ3CZWE123456"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"vehicle": {
|
||||
"id": "veh_123",
|
||||
"vin": "WVWZZZ3CZWE123456",
|
||||
"brand": {
|
||||
"code": "VOLKSWAGEN",
|
||||
"name": "Volkswagen"
|
||||
},
|
||||
"model": "Golf",
|
||||
"year": 2020,
|
||||
"series": "Golf 8",
|
||||
"bodyType": "Hatchback",
|
||||
"engineCode": "DFYA",
|
||||
"engineType": "Benzin",
|
||||
"engineVolume": "1.5L",
|
||||
"transmission": "DSG",
|
||||
"driveType": "FWD",
|
||||
"categories": [
|
||||
{
|
||||
"category": {
|
||||
"code": "ENGINE",
|
||||
"nameEn": "Engine",
|
||||
"nameTr": "Motor"
|
||||
},
|
||||
"partCount": 42
|
||||
}
|
||||
]
|
||||
},
|
||||
"fromCache": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- `403 Forbidden` - No access to this brand
|
||||
- `400 Bad Request` - Invalid VIN format
|
||||
|
||||
---
|
||||
|
||||
### GET `/vehicles`
|
||||
|
||||
Get user's previously queried vehicles.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Query Parameters:**
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| page | number | 1 | Page number |
|
||||
| limit | number | 20 | Items per page |
|
||||
| sortBy | string | createdAt | Sort field |
|
||||
| sortOrder | string | desc | Sort direction |
|
||||
| brandId | string | - | Filter by brand |
|
||||
| year | number | - | Filter by year |
|
||||
| search | string | - | Search in VIN/model |
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "veh_123",
|
||||
"vin": "WVWZZZ3CZWE123456",
|
||||
"brand": { "code": "VOLKSWAGEN", "name": "Volkswagen" },
|
||||
"model": "Golf",
|
||||
"year": 2020
|
||||
}
|
||||
],
|
||||
"total": 15,
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"totalPages": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/vehicles/:id`
|
||||
|
||||
Get vehicle details by ID.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token + Brand Access)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "veh_123",
|
||||
"vin": "WVWZZZ3CZWE123456",
|
||||
"brand": { "code": "VOLKSWAGEN", "name": "Volkswagen" },
|
||||
"model": "Golf",
|
||||
"year": 2020,
|
||||
"categories": [ ... ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DELETE `/vehicles/:id`
|
||||
|
||||
Remove vehicle from user's history.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"message": "Arac gecmisinizden kaldirildi"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/vehicles/:id/categories`
|
||||
|
||||
Get categories for a vehicle.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "cat_123",
|
||||
"code": "ENGINE",
|
||||
"nameEn": "Engine",
|
||||
"nameTr": "Motor",
|
||||
"partCount": 42
|
||||
}
|
||||
],
|
||||
"total": 8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/vehicles/:id/parts`
|
||||
|
||||
Get all parts for a vehicle.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Query Parameters:**
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| page | number | 1 | Page number |
|
||||
| limit | number | 20 | Items per page |
|
||||
|
||||
---
|
||||
|
||||
## Brands
|
||||
|
||||
### GET `/brands`
|
||||
|
||||
List all available brands.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": "br_1", "code": "BMW", "name": "BMW", "logo": "/logos/bmw.png" },
|
||||
{ "id": "br_2", "code": "AUDI", "name": "Audi", "logo": "/logos/audi.png" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/brands/selected`
|
||||
|
||||
Get user's selected brands.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
---
|
||||
|
||||
### PUT `/brands/selected`
|
||||
|
||||
Update user's brand selection.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"brandIds": ["br_1", "br_2", "br_3"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Subscriptions
|
||||
|
||||
### GET `/subscriptions/plans`
|
||||
|
||||
List available subscription plans.
|
||||
|
||||
**Auth Required:** No
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "plan_free",
|
||||
"name": "Ucretsiz",
|
||||
"slug": "free",
|
||||
"price": 0,
|
||||
"brandLimit": 1,
|
||||
"hasFullAccess": false,
|
||||
"features": ["1 marka secimi", "Sinirli sorgulama"]
|
||||
},
|
||||
{
|
||||
"id": "plan_premium",
|
||||
"name": "Premium",
|
||||
"slug": "premium",
|
||||
"price": 299,
|
||||
"brandLimit": 5,
|
||||
"hasFullAccess": false,
|
||||
"features": ["5 marka secimi", "Sinirsiz sorgulama"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET `/subscriptions/current`
|
||||
|
||||
Get current user's subscription.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
---
|
||||
|
||||
### POST `/subscriptions`
|
||||
|
||||
Create or upgrade subscription.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"planId": "plan_premium"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST `/subscriptions/cancel`
|
||||
|
||||
Cancel current subscription.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
---
|
||||
|
||||
## Payments
|
||||
|
||||
### POST `/payments/initialize`
|
||||
|
||||
Initialize 3D Secure payment.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"planId": "plan_premium",
|
||||
"card": {
|
||||
"cardHolderName": "John Doe",
|
||||
"cardNumber": "4111111111111111",
|
||||
"expireMonth": "12",
|
||||
"expireYear": "2028",
|
||||
"cvc": "123"
|
||||
},
|
||||
"buyer": {
|
||||
"name": "John",
|
||||
"surname": "Doe",
|
||||
"phone": "+905551234567",
|
||||
"identityNumber": "12345678901",
|
||||
"address": "Istanbul, Turkey",
|
||||
"city": "Istanbul",
|
||||
"country": "Turkey"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"status": "success",
|
||||
"threeDSHtmlContent": "<html>...</html>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST `/payments/callback`
|
||||
|
||||
iyzico payment callback (internal use).
|
||||
|
||||
---
|
||||
|
||||
## Parts
|
||||
|
||||
### GET `/parts/search`
|
||||
|
||||
Search parts by OEM code.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Query Parameters:**
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| oem | string | OEM code to search |
|
||||
| vehicleId | string | Filter by vehicle |
|
||||
| categoryId | string | Filter by category |
|
||||
| search | string | Search in name |
|
||||
|
||||
---
|
||||
|
||||
### GET `/parts/:id`
|
||||
|
||||
Get part details.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
---
|
||||
|
||||
## Categories
|
||||
|
||||
### GET `/categories`
|
||||
|
||||
List all categories.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
---
|
||||
|
||||
### GET `/categories/tree`
|
||||
|
||||
Get category hierarchy tree.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
---
|
||||
|
||||
### GET `/categories/:vehicleId/:categoryId/parts`
|
||||
|
||||
Get parts for a specific vehicle category.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
---
|
||||
|
||||
## Users (Admin)
|
||||
|
||||
### GET `/users`
|
||||
|
||||
List all users (admin only).
|
||||
|
||||
**Auth Required:** Yes (Bearer Token, Admin Role)
|
||||
|
||||
---
|
||||
|
||||
### GET `/users/profile`
|
||||
|
||||
Get own profile.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
---
|
||||
|
||||
### PATCH `/users/profile`
|
||||
|
||||
Update own profile.
|
||||
|
||||
**Auth Required:** Yes (Bearer Token)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "New Name",
|
||||
"avatar": "https://example.com/avatar.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | HTTP Status | Description |
|
||||
|------|-------------|-------------|
|
||||
| `UNAUTHORIZED` | 401 | Invalid or missing authentication |
|
||||
| `FORBIDDEN` | 403 | Insufficient permissions |
|
||||
| `NOT_FOUND` | 404 | Resource not found |
|
||||
| `CONFLICT` | 409 | Resource already exists |
|
||||
| `BAD_REQUEST` | 400 | Invalid request data |
|
||||
| `RATE_LIMITED` | 429 | Too many requests |
|
||||
| `INTERNAL_ERROR` | 500 | Server error |
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-01-16*
|
||||
475
docs/DEVELOPMENT.md
Normal file
475
docs/DEVELOPMENT.md
Normal file
@@ -0,0 +1,475 @@
|
||||
# Sase.tr Development Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js >= 20.0.0
|
||||
- pnpm >= 9.0.0
|
||||
- MySQL 8.0+
|
||||
- Redis 6.0+
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/your-org/sase.tr.git
|
||||
cd sase.tr
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Setup environment files
|
||||
cp apps/api/.env.example apps/api/.env
|
||||
cp apps/web/.env.example apps/web/.env
|
||||
|
||||
# Generate Prisma client
|
||||
pnpm db:generate
|
||||
|
||||
# Push database schema
|
||||
pnpm db:push
|
||||
|
||||
# Seed database (optional)
|
||||
pnpm db:seed
|
||||
|
||||
# Start development servers
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
**URLs:**
|
||||
- Web: http://localhost:3000
|
||||
- API: http://localhost:3001
|
||||
- Prisma Studio: http://localhost:5555
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
sase.tr/
|
||||
├── apps/
|
||||
│ ├── api/ # NestJS Backend
|
||||
│ │ ├── prisma/
|
||||
│ │ │ ├── schema.prisma # Database schema
|
||||
│ │ │ └── seed.ts # Database seeder
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── common/ # Shared utilities
|
||||
│ │ │ │ ├── decorators/ # Custom decorators
|
||||
│ │ │ │ ├── dto/ # Common DTOs
|
||||
│ │ │ │ ├── filters/ # Exception filters
|
||||
│ │ │ │ ├── guards/ # Auth guards
|
||||
│ │ │ │ ├── interceptors/
|
||||
│ │ │ │ └── pipes/ # Validation pipes
|
||||
│ │ │ ├── integrations/ # External services
|
||||
│ │ │ ├── modules/ # Feature modules
|
||||
│ │ │ ├── prisma/ # Database service
|
||||
│ │ │ ├── redis/ # Cache service
|
||||
│ │ │ ├── app.module.ts # Root module
|
||||
│ │ │ └── main.ts # Entry point
|
||||
│ │ └── test/ # Tests
|
||||
│ └── web/ # Next.js Frontend
|
||||
│ ├── app/ # App Router
|
||||
│ ├── components/ # React components
|
||||
│ ├── hooks/ # Custom hooks
|
||||
│ ├── lib/ # Utilities
|
||||
│ ├── providers/ # Context providers
|
||||
│ └── styles/ # Global styles
|
||||
├── packages/
|
||||
│ └── shared/ # Shared package
|
||||
│ └── src/
|
||||
│ └── utils/ # Common utilities
|
||||
├── scripts/ # Automation scripts
|
||||
├── docs/ # Documentation
|
||||
└── turbo.json # Turborepo config
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Adding a New API Module
|
||||
|
||||
1. **Create module structure:**
|
||||
|
||||
```bash
|
||||
cd apps/api/src/modules
|
||||
mkdir new-feature
|
||||
cd new-feature
|
||||
touch new-feature.module.ts
|
||||
touch new-feature.controller.ts
|
||||
touch new-feature.service.ts
|
||||
mkdir dto
|
||||
touch dto/create-feature.dto.ts
|
||||
```
|
||||
|
||||
2. **Define the module:**
|
||||
|
||||
```typescript
|
||||
// new-feature.module.ts
|
||||
import { Module } from '@nestjs/common';
|
||||
import { NewFeatureController } from './new-feature.controller';
|
||||
import { NewFeatureService } from './new-feature.service';
|
||||
|
||||
@Module({
|
||||
controllers: [NewFeatureController],
|
||||
providers: [NewFeatureService],
|
||||
exports: [NewFeatureService],
|
||||
})
|
||||
export class NewFeatureModule {}
|
||||
```
|
||||
|
||||
3. **Register in app.module.ts:**
|
||||
|
||||
```typescript
|
||||
import { NewFeatureModule } from './modules/new-feature/new-feature.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// ...existing modules
|
||||
NewFeatureModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
### Adding a New Frontend Page
|
||||
|
||||
1. **Create page file:**
|
||||
|
||||
```bash
|
||||
mkdir -p apps/web/app/dashboard/new-page
|
||||
touch apps/web/app/dashboard/new-page/page.tsx
|
||||
```
|
||||
|
||||
2. **Implement the page:**
|
||||
|
||||
```tsx
|
||||
// app/dashboard/new-page/page.tsx
|
||||
'use client';
|
||||
|
||||
export default function NewPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>New Page</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
3. **Add navigation link (if needed):**
|
||||
|
||||
Edit `apps/web/app/dashboard/layout.tsx` to add sidebar link.
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### API Response Wrapper
|
||||
|
||||
All API responses are automatically wrapped by `TransformInterceptor`:
|
||||
|
||||
```typescript
|
||||
// Input from service
|
||||
return { id: 1, name: 'Test' };
|
||||
|
||||
// Output to client
|
||||
{
|
||||
"success": true,
|
||||
"data": { "id": 1, "name": "Test" },
|
||||
"timestamp": "2026-01-16T12:00:00.000Z",
|
||||
"path": "/api/endpoint"
|
||||
}
|
||||
```
|
||||
|
||||
### Protected Routes
|
||||
|
||||
```typescript
|
||||
// Require authentication
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('protected')
|
||||
async protectedRoute() { ... }
|
||||
|
||||
// Public route
|
||||
@Public()
|
||||
@Get('public')
|
||||
async publicRoute() { ... }
|
||||
|
||||
// Role-based access
|
||||
@Roles(Role.ADMIN)
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Get('admin-only')
|
||||
async adminRoute() { ... }
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```typescript
|
||||
// Custom rate limit for specific endpoint
|
||||
@Throttle({ default: { limit: 3, ttl: 60000 } })
|
||||
@Post('sensitive')
|
||||
async sensitiveEndpoint() { ... }
|
||||
```
|
||||
|
||||
### DTO Validation
|
||||
|
||||
```typescript
|
||||
// dto/create-feature.dto.ts
|
||||
import { IsString, IsNotEmpty, MinLength } from 'class-validator';
|
||||
|
||||
export class CreateFeatureDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(3)
|
||||
name: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Prisma Queries
|
||||
|
||||
```typescript
|
||||
// Service method
|
||||
async findAll(pagination: PaginationDto) {
|
||||
const { page = 1, limit = 20 } = pagination;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.model.findMany({
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
this.prisma.model.count(),
|
||||
]);
|
||||
|
||||
return new PaginatedResponseDto(items, total, page, limit);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pnpm test
|
||||
|
||||
# Run with coverage
|
||||
pnpm --filter api test:cov
|
||||
|
||||
# Watch mode
|
||||
pnpm --filter api test:watch
|
||||
```
|
||||
|
||||
### E2E Tests
|
||||
|
||||
```bash
|
||||
# Run E2E tests
|
||||
pnpm --filter api test:e2e
|
||||
```
|
||||
|
||||
### Manual Testing with Puppeteer
|
||||
|
||||
```bash
|
||||
# Run full journey test
|
||||
node scripts/test-full-journey.mjs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Operations
|
||||
|
||||
### Schema Changes
|
||||
|
||||
1. Edit `apps/api/prisma/schema.prisma`
|
||||
2. Generate migration:
|
||||
```bash
|
||||
pnpm --filter api db:migrate:dev -- --name your_migration_name
|
||||
```
|
||||
3. Apply migration:
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
### Seeding
|
||||
|
||||
```bash
|
||||
# Run seed script
|
||||
pnpm db:seed
|
||||
```
|
||||
|
||||
The seed script (`apps/api/prisma/seed.ts`) creates:
|
||||
- Default subscription plans
|
||||
- Sample brands
|
||||
- Admin user
|
||||
|
||||
### Prisma Studio
|
||||
|
||||
```bash
|
||||
# Open visual database editor
|
||||
pnpm db:studio
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging
|
||||
|
||||
### API Debugging
|
||||
|
||||
1. **Enable debug mode:**
|
||||
```bash
|
||||
pnpm --filter api start:debug
|
||||
```
|
||||
|
||||
2. **VS Code launch config:**
|
||||
```json
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach NestJS",
|
||||
"port": 9229,
|
||||
"restart": true
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend Debugging
|
||||
|
||||
Next.js includes built-in debugging. Use browser DevTools or:
|
||||
|
||||
```bash
|
||||
NODE_OPTIONS='--inspect' pnpm --filter web dev
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
API uses NestJS Logger:
|
||||
|
||||
```typescript
|
||||
private readonly logger = new Logger(MyService.name);
|
||||
|
||||
this.logger.log('Info message');
|
||||
this.logger.warn('Warning message');
|
||||
this.logger.error('Error message', error.stack);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Required Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `DATABASE_URL` | MySQL connection string |
|
||||
| `JWT_SECRET` | JWT signing secret |
|
||||
| `JWT_REFRESH_SECRET` | Refresh token secret |
|
||||
| `REDIS_HOST` | Redis host |
|
||||
|
||||
### Optional Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `JWT_EXPIRES_IN` | 15m | Access token expiry |
|
||||
| `JWT_REFRESH_EXPIRES_IN` | 7d | Refresh token expiry |
|
||||
| `REDIS_PORT` | 6379 | Redis port |
|
||||
| `VIN_API_TIMEOUT` | 30000 | API timeout (ms) |
|
||||
|
||||
---
|
||||
|
||||
## Code Style
|
||||
|
||||
### TypeScript
|
||||
|
||||
- Use explicit types (avoid `any`)
|
||||
- Use interfaces for object shapes
|
||||
- Use enums for fixed sets of values
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
| Type | Convention | Example |
|
||||
|------|------------|---------|
|
||||
| Files | kebab-case | `user-service.ts` |
|
||||
| Classes | PascalCase | `UserService` |
|
||||
| Functions | camelCase | `getUserById` |
|
||||
| Constants | SCREAMING_SNAKE | `MAX_RETRY_COUNT` |
|
||||
| Interfaces | PascalCase (I prefix optional) | `UserData` |
|
||||
|
||||
### Formatting
|
||||
|
||||
```bash
|
||||
# Format all files
|
||||
pnpm format
|
||||
|
||||
# Lint all files
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# Build all apps
|
||||
pnpm build
|
||||
|
||||
# Build specific app
|
||||
pnpm build --filter=api
|
||||
pnpm build --filter=web
|
||||
```
|
||||
|
||||
### Production Start
|
||||
|
||||
```bash
|
||||
# API
|
||||
cd apps/api && node dist/main.js
|
||||
|
||||
# Web
|
||||
cd apps/web && npm start
|
||||
```
|
||||
|
||||
### PM2 (Recommended)
|
||||
|
||||
```bash
|
||||
# Start services
|
||||
pm2 start ecosystem.config.js
|
||||
|
||||
# Monitor
|
||||
pm2 monit
|
||||
|
||||
# Logs
|
||||
pm2 logs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**1. Prisma client not generated:**
|
||||
```bash
|
||||
pnpm db:generate
|
||||
```
|
||||
|
||||
**2. Database connection failed:**
|
||||
- Check `DATABASE_URL` format
|
||||
- Verify MySQL is running
|
||||
- Check network/firewall rules
|
||||
|
||||
**3. Redis connection failed:**
|
||||
- Verify Redis is running
|
||||
- Check `REDIS_HOST` and `REDIS_PORT`
|
||||
|
||||
**4. JWT errors:**
|
||||
- Ensure `JWT_SECRET` is set
|
||||
- Check token expiration
|
||||
- Verify token format in requests
|
||||
|
||||
**5. CORS errors:**
|
||||
- Check `CORS_ORIGIN` in API `.env`
|
||||
- Ensure frontend URL matches
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-01-16*
|
||||
441
docs/PROJECT_INDEX.md
Normal file
441
docs/PROJECT_INDEX.md
Normal file
@@ -0,0 +1,441 @@
|
||||
# Sase.tr - VIN Query SaaS Platform
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Sase.tr** is a Turkish VIN (Vehicle Identification Number) query SaaS platform that allows users to look up vehicle information and spare parts by entering a VIN. The platform offers subscription-based access with brand-specific permissions.
|
||||
|
||||
### Tech Stack
|
||||
|
||||
| Layer | Technology | Version |
|
||||
|-------|-----------|---------|
|
||||
| **Monorepo** | Turborepo + pnpm | 2.7.4 / 9.15.0 |
|
||||
| **Backend** | NestJS | 10.4.x |
|
||||
| **Frontend** | Next.js (App Router) | 15.1.0 |
|
||||
| **Database** | MySQL + Prisma ORM | 6.1.x |
|
||||
| **Cache** | Redis (ioredis) | 5.4.x |
|
||||
| **Auth** | JWT (dual-token) | passport-jwt |
|
||||
| **UI** | Tailwind CSS + Radix UI | 3.4.x |
|
||||
| **State** | Zustand + React Query | 5.x / 5.62.x |
|
||||
| **Payments** | iyzico (3D Secure) | Custom integration |
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
sase.tr/
|
||||
├── apps/
|
||||
│ ├── api/ # NestJS Backend (Port 3001)
|
||||
│ │ ├── prisma/ # Database schema & migrations
|
||||
│ │ └── src/
|
||||
│ │ ├── common/ # Shared utilities
|
||||
│ │ ├── integrations/ # External API integrations
|
||||
│ │ ├── modules/ # Feature modules
|
||||
│ │ ├── prisma/ # Prisma service
|
||||
│ │ └── redis/ # Redis service
|
||||
│ └── web/ # Next.js Frontend (Port 3000)
|
||||
│ ├── app/ # App Router pages
|
||||
│ ├── components/ # UI components
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ └── lib/ # Utilities
|
||||
├── packages/
|
||||
│ └── shared/ # Shared TypeScript utilities
|
||||
├── docs/ # Documentation
|
||||
└── scripts/ # Automation scripts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Entity Relationship Diagram
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────────┐ ┌────────────┐
|
||||
│ User │────<│ UserSubscription │>────│ Plan │
|
||||
├─────────────┤ ├──────────────────┤ ├────────────┤
|
||||
│ id │ │ id │ │ id │
|
||||
│ email │ │ userId │ │ name │
|
||||
│ name │ │ planId │ │ price │
|
||||
│ role │ │ status │ │ brandLimit │
|
||||
│ passwordHash│ │ currentPeriodEnd │ │ features │
|
||||
└─────────────┘ └──────────────────┘ └────────────┘
|
||||
│ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────┐ ┌──────────────────┐
|
||||
│ UserBrand │ │ Payment │
|
||||
├─────────────┤ ├──────────────────┤
|
||||
│ userId │ │ subscriptionId │
|
||||
│ brandId │ │ amount │
|
||||
└─────────────┘ │ status │
|
||||
│ └──────────────────┘
|
||||
▼
|
||||
┌─────────────┐ ┌──────────────────┐ ┌────────────┐
|
||||
│ Brand │────<│ Vehicle │>────│ QueryLog │
|
||||
├─────────────┤ ├──────────────────┤ ├────────────┤
|
||||
│ id │ │ id │ │ userId │
|
||||
│ code │ │ vin │ │ vehicleId │
|
||||
│ name │ │ brandId │ │ vin │
|
||||
└─────────────┘ │ model, year │ └────────────┘
|
||||
└──────────────────┘
|
||||
│
|
||||
┌────────────────┼────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌────────────┐ ┌──────────────┐
|
||||
│ VehicleCategory │ │ Part │ │ Category │
|
||||
├─────────────────┤ ├────────────┤ ├──────────────┤
|
||||
│ vehicleId │ │ vehicleId │ │ id │
|
||||
│ categoryId │ │ categoryId │ │ code │
|
||||
│ partCount │ │ oemCode │ │ nameEn/nameTr│
|
||||
└─────────────────┘ │ brandPrices│ │ parentId │
|
||||
└────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### Role Hierarchy
|
||||
|
||||
```
|
||||
SUPER_ADMIN > ADMIN > MODERATOR > USER
|
||||
```
|
||||
|
||||
| Role | Permissions |
|
||||
|------|-------------|
|
||||
| `USER` | Query vehicles, manage own profile, view subscription |
|
||||
| `MODERATOR` | + Manage vehicles, moderate content |
|
||||
| `ADMIN` | + Manage users, brands, plans |
|
||||
| `SUPER_ADMIN` | Full system access |
|
||||
|
||||
---
|
||||
|
||||
## API Modules
|
||||
|
||||
### Authentication (`/api/auth`)
|
||||
|
||||
| Endpoint | Method | Auth | Rate Limit | Description |
|
||||
|----------|--------|------|------------|-------------|
|
||||
| `/register` | POST | Public | 3/min | Create new user account |
|
||||
| `/login` | POST | Public | 5/min | Authenticate user |
|
||||
| `/refresh` | POST | Public | 10/min | Refresh access token |
|
||||
| `/logout` | POST | JWT | - | Logout user |
|
||||
| `/forgot-password` | POST | Public | 3/min | Request password reset |
|
||||
| `/reset-password` | POST | Public | 5/min | Reset password with token |
|
||||
| `/me` | GET | JWT | - | Get current user profile |
|
||||
|
||||
**Files:**
|
||||
- `auth.controller.ts` - Route handlers
|
||||
- `auth.service.ts` - Business logic
|
||||
- `jwt.strategy.ts` - JWT validation
|
||||
- `jwt-refresh.strategy.ts` - Refresh token validation
|
||||
|
||||
### Vehicles (`/api/vehicles`)
|
||||
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/decode` | POST | JWT + Brand | Decode VIN and return vehicle data |
|
||||
| `/` | GET | JWT | Get user's queried vehicles |
|
||||
| `/:id` | GET | JWT + Brand | Get vehicle by ID |
|
||||
| `/:id` | DELETE | JWT | Remove vehicle from history |
|
||||
| `/:id/categories` | GET | JWT | Get vehicle categories |
|
||||
| `/:id/parts` | GET | JWT | Get vehicle parts |
|
||||
|
||||
**Files:**
|
||||
- `vehicles.controller.ts` - Route handlers
|
||||
- `vehicles.service.ts` - Vehicle CRUD operations
|
||||
- `vin-decoder.service.ts` - VIN decoding logic
|
||||
|
||||
### Brands (`/api/brands`)
|
||||
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/` | GET | JWT | List all brands |
|
||||
| `/selected` | GET | JWT | Get user's selected brands |
|
||||
| `/selected` | PUT | JWT | Update brand selection |
|
||||
|
||||
### Subscriptions (`/api/subscriptions`)
|
||||
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/plans` | GET | Public | List available plans |
|
||||
| `/current` | GET | JWT | Get current subscription |
|
||||
| `/` | POST | JWT | Create new subscription |
|
||||
| `/cancel` | POST | JWT | Cancel subscription |
|
||||
|
||||
### Payments (`/api/payments`)
|
||||
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/initialize` | POST | JWT | Initialize 3D Secure payment |
|
||||
| `/callback` | POST | Public | iyzico payment callback |
|
||||
|
||||
### Users (`/api/users`)
|
||||
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/profile` | GET | JWT | Get user profile |
|
||||
| `/profile` | PATCH | JWT | Update profile |
|
||||
| `/` | GET | Admin | List all users |
|
||||
|
||||
### Parts (`/api/parts`)
|
||||
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/search` | GET | JWT | Search parts by OEM code |
|
||||
| `/:id` | GET | JWT | Get part details |
|
||||
|
||||
### Categories (`/api/categories`)
|
||||
|
||||
| Endpoint | Method | Auth | Description |
|
||||
|----------|--------|------|-------------|
|
||||
| `/` | GET | JWT | List all categories |
|
||||
| `/tree` | GET | JWT | Get category tree |
|
||||
| `/:vehicleId/:categoryId/parts` | GET | JWT | Get parts by category |
|
||||
|
||||
---
|
||||
|
||||
## Frontend Pages
|
||||
|
||||
### Public Routes
|
||||
|
||||
| Path | Component | Description |
|
||||
|------|-----------|-------------|
|
||||
| `/` | `(marketing)/page.tsx` | Landing page |
|
||||
| `/login` | `(auth)/login/page.tsx` | User login |
|
||||
| `/register` | `(auth)/register/page.tsx` | User registration |
|
||||
| `/forgot-password` | `(auth)/forgot-password/page.tsx` | Password recovery |
|
||||
|
||||
### Protected Routes (Dashboard)
|
||||
|
||||
| Path | Component | Description |
|
||||
|------|-----------|-------------|
|
||||
| `/dashboard` | `dashboard/page.tsx` | User dashboard overview |
|
||||
| `/dashboard/vehicles/search` | `vehicles/search/page.tsx` | VIN search interface |
|
||||
| `/dashboard/vehicles` | `vehicles/page.tsx` | Vehicle history |
|
||||
| `/dashboard/vehicles/[vin]` | `vehicles/[vin]/page.tsx` | Vehicle details |
|
||||
| `/dashboard/subscription` | `subscription/page.tsx` | Subscription management |
|
||||
| `/dashboard/profile` | `profile/page.tsx` | User profile settings |
|
||||
|
||||
---
|
||||
|
||||
## Security Features
|
||||
|
||||
### Authentication
|
||||
- **JWT Dual-Token System**: Access token (15min) + Refresh token (7d)
|
||||
- **Password Hashing**: bcrypt with 12 rounds
|
||||
- **Token Blacklisting**: Redis-based refresh token blacklist
|
||||
|
||||
### Authorization
|
||||
- **RBAC**: Role-based access control with hierarchy
|
||||
- **Brand Access Guard**: Subscription-based brand permissions
|
||||
- **JWT Auth Guard**: Global authentication protection
|
||||
|
||||
### Rate Limiting
|
||||
- **Global**: 100 requests/minute
|
||||
- **Auth Endpoints**: 3-10 requests/minute (endpoint-specific)
|
||||
- **Throttler Guard**: Applied globally via `@nestjs/throttler`
|
||||
|
||||
### Input Validation
|
||||
- **class-validator**: DTO validation decorators
|
||||
- **VIN Validation Pipe**: 17-character VIN format validation
|
||||
- **Prisma Exception Filter**: Database error sanitization
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### API (`apps/api/.env`)
|
||||
|
||||
```env
|
||||
# Database
|
||||
DATABASE_URL=mysql://user:pass@host:3306/database
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your-jwt-secret
|
||||
JWT_EXPIRES_IN=15m
|
||||
JWT_REFRESH_SECRET=your-refresh-secret
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=optional
|
||||
|
||||
# External APIs
|
||||
VIN_API_URL=https://api.vinprovider.com/v1
|
||||
VIN_API_KEY=your-api-key
|
||||
VIN_API_TIMEOUT=30000
|
||||
|
||||
# iyzico Payment
|
||||
IYZICO_API_KEY=your-iyzico-key
|
||||
IYZICO_SECRET_KEY=your-iyzico-secret
|
||||
IYZICO_BASE_URL=https://sandbox-api.iyzipay.com
|
||||
IYZICO_CALLBACK_URL=https://sase.tr/api/payments/callback
|
||||
|
||||
# CORS
|
||||
CORS_ORIGIN=https://sase.tr
|
||||
```
|
||||
|
||||
### Web (`apps/web/.env`)
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_API_URL=https://sase.tr/api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Root Commands
|
||||
|
||||
```bash
|
||||
pnpm dev # Start all apps in development
|
||||
pnpm build # Build all apps
|
||||
pnpm lint # Lint all apps
|
||||
pnpm format # Format code with Prettier
|
||||
pnpm clean # Clean build artifacts
|
||||
```
|
||||
|
||||
### Database Commands
|
||||
|
||||
```bash
|
||||
pnpm db:generate # Generate Prisma client
|
||||
pnpm db:push # Push schema to database
|
||||
pnpm db:migrate # Run migrations
|
||||
pnpm db:seed # Seed database
|
||||
pnpm db:studio # Open Prisma Studio
|
||||
```
|
||||
|
||||
### Individual App Commands
|
||||
|
||||
```bash
|
||||
# API
|
||||
pnpm --filter api dev
|
||||
pnpm --filter api build
|
||||
pnpm --filter api test
|
||||
|
||||
# Web
|
||||
pnpm --filter web dev
|
||||
pnpm --filter web build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Production Architecture
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ Cloudflare │
|
||||
│ (DNS) │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ Nginx │
|
||||
│ (Reverse) │
|
||||
└──────┬──────┘
|
||||
┌────────────┼────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Web │ │ API │ │ API │
|
||||
│ :3000 │ │ :3001 │ │ :3001 │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ PM2 Process Manager │
|
||||
└─────────────────────────────────────┘
|
||||
│ │
|
||||
┌──────▼──────┐ ┌──────▼──────┐
|
||||
│ MySQL │ │ Redis │
|
||||
│ Database │ │ Cache │
|
||||
└─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
### PM2 Configuration
|
||||
|
||||
```bash
|
||||
pm2 start sase-api # API (cluster mode, 2 instances)
|
||||
pm2 start sase-web # Web (single instance)
|
||||
pm2 restart all # Restart all services
|
||||
pm2 logs # View logs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Index
|
||||
|
||||
### API Source Files
|
||||
|
||||
#### Common
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `common/decorators/current-user.decorator.ts` | Extract user from JWT |
|
||||
| `common/decorators/public.decorator.ts` | Mark routes as public |
|
||||
| `common/decorators/roles.decorator.ts` | Role-based access decorator |
|
||||
| `common/guards/jwt-auth.guard.ts` | JWT authentication guard |
|
||||
| `common/guards/brand-access.guard.ts` | Brand permission guard |
|
||||
| `common/guards/roles.guard.ts` | Role hierarchy guard |
|
||||
| `common/filters/http-exception.filter.ts` | HTTP exception handler |
|
||||
| `common/filters/prisma-exception.filter.ts` | Prisma error handler |
|
||||
| `common/interceptors/transform.interceptor.ts` | Response transformation |
|
||||
| `common/interceptors/logging.interceptor.ts` | Request logging |
|
||||
| `common/interceptors/timeout.interceptor.ts` | Request timeout |
|
||||
| `common/pipes/vin-validation.pipe.ts` | VIN format validation |
|
||||
| `common/dto/pagination.dto.ts` | Pagination helpers |
|
||||
| `common/dto/api-response.dto.ts` | API response wrapper |
|
||||
|
||||
#### Modules
|
||||
| Module | Files |
|
||||
|--------|-------|
|
||||
| **Auth** | `auth.module.ts`, `auth.controller.ts`, `auth.service.ts`, `jwt.strategy.ts`, `jwt-refresh.strategy.ts`, DTOs |
|
||||
| **Users** | `users.module.ts`, `users.controller.ts`, `users.service.ts`, DTOs |
|
||||
| **Brands** | `brands.module.ts`, `brands.controller.ts`, `brands.service.ts`, DTOs |
|
||||
| **Vehicles** | `vehicles.module.ts`, `vehicles.controller.ts`, `vehicles.service.ts`, `vin-decoder.service.ts`, DTOs |
|
||||
| **Parts** | `parts.module.ts`, `parts.controller.ts`, `parts.service.ts`, `categories.controller.ts`, `categories.service.ts`, DTOs |
|
||||
| **Subscriptions** | `subscriptions.module.ts`, `subscriptions.controller.ts`, `subscriptions.service.ts`, `plans.service.ts`, DTOs |
|
||||
| **Payments** | `payments.module.ts`, `payments.controller.ts`, `payments.service.ts`, `iyzico.service.ts`, DTOs |
|
||||
|
||||
#### Integrations
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `integrations/vin-api/vin-api.service.ts` | External VIN API client |
|
||||
| `integrations/vin-api/vin-api.types.ts` | API response types |
|
||||
| `integrations/vin-api/vin-api.mapper.ts` | Response transformation |
|
||||
|
||||
### Web Source Files
|
||||
|
||||
#### Pages
|
||||
| File | Route |
|
||||
|------|-------|
|
||||
| `app/(marketing)/page.tsx` | `/` |
|
||||
| `app/(auth)/login/page.tsx` | `/login` |
|
||||
| `app/(auth)/register/page.tsx` | `/register` |
|
||||
| `app/(auth)/forgot-password/page.tsx` | `/forgot-password` |
|
||||
| `app/dashboard/page.tsx` | `/dashboard` |
|
||||
| `app/dashboard/vehicles/search/page.tsx` | `/dashboard/vehicles/search` |
|
||||
| `app/dashboard/vehicles/page.tsx` | `/dashboard/vehicles` |
|
||||
| `app/dashboard/vehicles/[vin]/page.tsx` | `/dashboard/vehicles/[vin]` |
|
||||
| `app/dashboard/subscription/page.tsx` | `/dashboard/subscription` |
|
||||
| `app/dashboard/profile/page.tsx` | `/dashboard/profile` |
|
||||
|
||||
#### Layouts
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `app/layout.tsx` | Root layout |
|
||||
| `app/(marketing)/layout.tsx` | Marketing layout |
|
||||
| `app/(auth)/layout.tsx` | Auth pages layout |
|
||||
| `app/dashboard/layout.tsx` | Dashboard layout with sidebar |
|
||||
|
||||
---
|
||||
|
||||
## Recent Security Fixes (2026-01-16)
|
||||
|
||||
1. **Password Reset Token Exposure** - Removed `console.log` that leaked reset tokens
|
||||
2. **Rate Limiting Enforcement** - Added per-endpoint throttling on auth routes
|
||||
3. **Type Safety** - Replaced 11 `any` types with proper TypeScript types
|
||||
4. **CORS Configuration** - Verified proper CORS setup with environment variables
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-01-16*
|
||||
*Generated by Claude Code*
|
||||
Reference in New Issue
Block a user