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:
root
2026-01-17 03:04:30 +01:00
commit b9532a61ee
195 changed files with 31777 additions and 0 deletions

681
docs/API_REFERENCE.md Normal file
View 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
View 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
View 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*