Add API documentation for Google authentication endpoint
This commit is contained in:
parent
08cc2bebbe
commit
579f8a3f21
946
API_DOCUMENTATION.md
Normal file
946
API_DOCUMENTATION.md
Normal file
@ -0,0 +1,946 @@
|
||||
# API Documentation
|
||||
|
||||
# Profitra API Documentation
|
||||
|
||||
## Base Information
|
||||
|
||||
**Base URL:** `https://api.profitra.id/v1`
|
||||
|
||||
**Development URL:** `http://localhost:8000/v1`
|
||||
|
||||
**Protocol:** HTTPS (Production), HTTP (Development)
|
||||
|
||||
**Content Type:** `application/json`
|
||||
|
||||
**Authentication:** JWT Bearer Token
|
||||
|
||||
---
|
||||
|
||||
## API Structure
|
||||
|
||||
### Endpoints Organization
|
||||
|
||||
```
|
||||
/v1
|
||||
├── /auth # Authentication
|
||||
├── /users # User management
|
||||
├── /tenants # Tenant management
|
||||
├── /plans # Subscription plans
|
||||
├── /subscriptions # Subscription management
|
||||
├── /business-types # Business types
|
||||
├── /products # Product management (tenant)
|
||||
├── /categories # Product categories (tenant)
|
||||
├── /customers # Customer management (tenant)
|
||||
├── /sales # Sales transactions (tenant)
|
||||
├── /inventory # Inventory management (tenant)
|
||||
├── /reports # Reports (tenant)
|
||||
└── /settings # Settings (tenant)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
### Register
|
||||
|
||||
**Endpoint:** `POST /auth/register`
|
||||
|
||||
**Description:** Create new user account
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"full_name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"username": "johndoe",
|
||||
"password": "securepass123",
|
||||
"phone": "081234567890",
|
||||
"timezone": "Asia/Jakarta"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation Rules:**
|
||||
- `full_name`: required, max 200 chars
|
||||
- `email`: required, valid email, unique, max 100 chars
|
||||
- `username`: required, alphanumeric, unique, 3-20 chars
|
||||
- `password`: required, min 8 chars
|
||||
- `phone`: optional, valid phone number
|
||||
- `timezone`: optional, default "Asia/Jakarta"
|
||||
|
||||
**Success Response (201):**
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "john@example.com",
|
||||
"username": "johndoe",
|
||||
"tenant_id": null,
|
||||
"status": "INACTIVE",
|
||||
"created_at": "2026-07-20T08:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (409):**
|
||||
```json
|
||||
{
|
||||
"message": "Email atau username sudah terdaftar."
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (400):**
|
||||
```json
|
||||
{
|
||||
"message": "Validation errors in your request",
|
||||
"errors": [
|
||||
{
|
||||
"field": "email",
|
||||
"message": "Invalid email format",
|
||||
"code": 1001
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Login
|
||||
|
||||
**Endpoint:** `POST /auth/login`
|
||||
|
||||
**Description:** Authenticate user and get JWT token
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"login": "johndoe",
|
||||
"password": "securepass123"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation Rules:**
|
||||
- `login`: required (username or email)
|
||||
- `password`: required
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "bearer",
|
||||
"user": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "john@example.com",
|
||||
"username": "johndoe",
|
||||
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"status": "ACTIVE",
|
||||
"created_at": "2026-07-20T08:00:00Z",
|
||||
"profile": {
|
||||
"full_name": "John Doe",
|
||||
"phone": "081234567890",
|
||||
"timezone": "Asia/Jakarta"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (401):**
|
||||
```json
|
||||
{
|
||||
"message": "Kredensial autentikasi tidak ada atau tidak valid."
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (401) - Email not verified:**
|
||||
```json
|
||||
{
|
||||
"message": "Email belum diverifikasi. Silakan verifikasi email terlebih dahulu."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Verify Email
|
||||
|
||||
**Endpoint:** `POST /auth/verify-email`
|
||||
|
||||
**Description:** Verify user email with token
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"token": "verification-token-here"
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"message": "Email berhasil diverifikasi",
|
||||
"user": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "john@example.com",
|
||||
"status": "ACTIVE",
|
||||
"email_verified_at": "2026-07-20T08:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (400):**
|
||||
```json
|
||||
{
|
||||
"message": "Token tidak valid atau sudah kadaluarsa"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Resend Verification Email
|
||||
|
||||
**Endpoint:** `POST /auth/resend-verification`
|
||||
|
||||
**Description:** Resend verification email
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"email": "john@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"message": "Email verifikasi telah dikirim ulang"
|
||||
}
|
||||
```
|
||||
|
||||
**Rate Limit:** 3 requests per hour per email
|
||||
|
||||
---
|
||||
|
||||
### Forgot Password
|
||||
|
||||
**Endpoint:** `POST /auth/forgot-password`
|
||||
|
||||
**Description:** Request password reset
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"email": "john@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"message": "Link reset password telah dikirim ke email Anda"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Reset Password
|
||||
|
||||
**Endpoint:** `POST /auth/reset-password`
|
||||
|
||||
**Description:** Reset password with token
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"token": "reset-token-here",
|
||||
"password": "newpassword123",
|
||||
"password_confirmation": "newpassword123"
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"message": "Password berhasil diubah"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Logout
|
||||
|
||||
**Endpoint:** `POST /auth/logout`
|
||||
|
||||
**Description:** Logout user (invalidate token if using refresh token)
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"message": "Logout berhasil"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Current User
|
||||
|
||||
**Endpoint:** `GET /auth/me`
|
||||
|
||||
**Description:** Get current authenticated user info
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"email": "john@example.com",
|
||||
"username": "johndoe",
|
||||
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"status": "ACTIVE",
|
||||
"profile": {
|
||||
"full_name": "John Doe",
|
||||
"phone": "081234567890",
|
||||
"timezone": "Asia/Jakarta"
|
||||
},
|
||||
"tenant": {
|
||||
"id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Toko Maju Jaya",
|
||||
"slug": "toko-maju-jaya",
|
||||
"business_type": "RETAIL"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Business Types
|
||||
|
||||
### List Business Types
|
||||
|
||||
**Endpoint:** `GET /business-types`
|
||||
|
||||
**Description:** Get all available business types
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "770e8400-e29b-41d4-a716-446655440000",
|
||||
"code": "RETAIL",
|
||||
"name": "Retail",
|
||||
"is_active": true,
|
||||
"created_at": "2026-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "880e8400-e29b-41d4-a716-446655440000",
|
||||
"code": "FNB",
|
||||
"name": "Food & Beverage",
|
||||
"is_active": true,
|
||||
"created_at": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Business Type
|
||||
|
||||
**Endpoint:** `GET /business-types/{id}`
|
||||
|
||||
**Description:** Get business type by ID
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": "770e8400-e29b-41d4-a716-446655440000",
|
||||
"code": "RETAIL",
|
||||
"name": "Retail",
|
||||
"is_active": true,
|
||||
"created_at": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (404):**
|
||||
```json
|
||||
{
|
||||
"message": "Business type not found"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Subscription Plans
|
||||
|
||||
### List Plans
|
||||
|
||||
**Endpoint:** `GET /plans`
|
||||
|
||||
**Description:** Get all available subscription plans
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "990e8400-e29b-41d4-a716-446655440000",
|
||||
"code": "FREE",
|
||||
"name": "Free Plan",
|
||||
"description": "30-day trial with basic features",
|
||||
"price": 0,
|
||||
"limits": {
|
||||
"products": 100,
|
||||
"customers": 100,
|
||||
"transactions_per_month": 500,
|
||||
"users": 1,
|
||||
"locations": 1
|
||||
},
|
||||
"is_active": true,
|
||||
"created_at": "2026-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "aa0e8400-e29b-41d4-a716-446655440000",
|
||||
"code": "BASIC",
|
||||
"name": "Basic Plan",
|
||||
"description": "For small to medium business",
|
||||
"price": 99000,
|
||||
"limits": {
|
||||
"products": 5000,
|
||||
"customers": -1,
|
||||
"transactions_per_month": 5000,
|
||||
"users": 5,
|
||||
"locations": 3
|
||||
},
|
||||
"is_active": true,
|
||||
"created_at": "2026-01-01T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "bb0e8400-e29b-41d4-a716-446655440000",
|
||||
"code": "PRO",
|
||||
"name": "Pro Plan",
|
||||
"description": "For medium to large business",
|
||||
"price": 299000,
|
||||
"limits": {
|
||||
"products": -1,
|
||||
"customers": -1,
|
||||
"transactions_per_month": -1,
|
||||
"users": -1,
|
||||
"locations": -1
|
||||
},
|
||||
"is_active": true,
|
||||
"created_at": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Note:** `-1` means unlimited
|
||||
|
||||
---
|
||||
|
||||
### Get Plan
|
||||
|
||||
**Endpoint:** `GET /plans/{id}`
|
||||
|
||||
**Description:** Get plan by ID
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": "990e8400-e29b-41d4-a716-446655440000",
|
||||
"code": "FREE",
|
||||
"name": "Free Plan",
|
||||
"description": "30-day trial with basic features",
|
||||
"price": 0,
|
||||
"limits": {
|
||||
"products": 100,
|
||||
"customers": 100,
|
||||
"transactions_per_month": 500,
|
||||
"users": 1,
|
||||
"locations": 1
|
||||
},
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tenants
|
||||
|
||||
### Create Tenant (Onboarding)
|
||||
|
||||
**Endpoint:** `POST /tenants`
|
||||
|
||||
**Description:** Create new tenant (business onboarding)
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "Toko Maju Jaya",
|
||||
"business_type_id": "770e8400-e29b-41d4-a716-446655440000",
|
||||
"phone": "0215551234",
|
||||
"email": "info@majujaya.com",
|
||||
"address": "Jl. Sudirman No. 123",
|
||||
"city": "Jakarta Pusat",
|
||||
"province": "DKI Jakarta",
|
||||
"postal_code": "10110",
|
||||
"plan_id": "990e8400-e29b-41d4-a716-446655440000",
|
||||
"settings": {
|
||||
"currency": "IDR",
|
||||
"timezone": "Asia/Jakarta",
|
||||
"tax_rate": 11,
|
||||
"fiscal_year_start": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (201):**
|
||||
```json
|
||||
{
|
||||
"id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Toko Maju Jaya",
|
||||
"slug": "toko-maju-jaya",
|
||||
"business_type_id": "770e8400-e29b-41d4-a716-446655440000",
|
||||
"phone": "0215551234",
|
||||
"email": "info@majujaya.com",
|
||||
"address": "Jl. Sudirman No. 123",
|
||||
"status": "ACTIVE",
|
||||
"created_at": "2026-07-20T08:00:00Z",
|
||||
"subscription": {
|
||||
"id": "cc0e8400-e29b-41d4-a716-446655440000",
|
||||
"plan": "FREE",
|
||||
"status": "TRIAL",
|
||||
"started_at": "2026-07-20T08:00:00Z",
|
||||
"ended_at": "2026-08-19T23:59:59Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Process:**
|
||||
1. Validate request data
|
||||
2. Generate unique slug
|
||||
3. Create tenant record in central DB
|
||||
4. Create tenant database
|
||||
5. Run migrations on tenant DB
|
||||
6. Seed default data
|
||||
7. Create subscription
|
||||
8. Link user to tenant
|
||||
9. Assign OWNER role to user in tenant DB
|
||||
10. Send welcome email
|
||||
|
||||
---
|
||||
|
||||
### Get Tenant
|
||||
|
||||
**Endpoint:** `GET /tenants/{id}`
|
||||
|
||||
**Description:** Get tenant details
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Toko Maju Jaya",
|
||||
"slug": "toko-maju-jaya",
|
||||
"business_type": {
|
||||
"code": "RETAIL",
|
||||
"name": "Retail"
|
||||
},
|
||||
"phone": "0215551234",
|
||||
"email": "info@majujaya.com",
|
||||
"address": "Jl. Sudirman No. 123",
|
||||
"status": "ACTIVE",
|
||||
"created_at": "2026-07-20T08:00:00Z",
|
||||
"subscription": {
|
||||
"plan": "FREE",
|
||||
"status": "TRIAL",
|
||||
"expires_at": "2026-08-19T23:59:59Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Update Tenant
|
||||
|
||||
**Endpoint:** `PUT /tenants/{id}`
|
||||
|
||||
**Description:** Update tenant information
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Permission Required:** `tenants.edit` or OWNER role
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "Toko Maju Jaya Sejahtera",
|
||||
"phone": "0215551235",
|
||||
"email": "info@majujaya.co.id",
|
||||
"address": "Jl. Sudirman No. 125"
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": "660e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Toko Maju Jaya Sejahtera",
|
||||
"slug": "toko-maju-jaya",
|
||||
"phone": "0215551235",
|
||||
"email": "info@majujaya.co.id",
|
||||
"address": "Jl. Sudirman No. 125",
|
||||
"updated_at": "2026-07-20T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Products (Tenant-specific)
|
||||
|
||||
**Note:** All product endpoints require tenant context from JWT token
|
||||
|
||||
### List Products
|
||||
|
||||
**Endpoint:** `GET /products`
|
||||
|
||||
**Description:** Get all products for current tenant
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
- `page`: integer, default 1
|
||||
- `limit`: integer, default 20, max 100
|
||||
- `search`: string, search in name/sku
|
||||
- `category_id`: uuid, filter by category
|
||||
- `status`: string, filter by status (active/inactive)
|
||||
- `sort`: string, sort field (name, price, stock)
|
||||
- `order`: string, sort order (asc/desc)
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "dd0e8400-e29b-41d4-a716-446655440000",
|
||||
"sku": "PRD-001",
|
||||
"name": "Product A",
|
||||
"description": "Description of product A",
|
||||
"category_id": "ee0e8400-e29b-41d4-a716-446655440000",
|
||||
"category_name": "Electronics",
|
||||
"unit": "pcs",
|
||||
"purchase_price": 40000,
|
||||
"selling_price": 50000,
|
||||
"stock": 100,
|
||||
"min_stock": 10,
|
||||
"status": "active",
|
||||
"image": "https://cdn.profitra.id/products/product-a.jpg",
|
||||
"created_at": "2026-07-20T08:00:00Z",
|
||||
"updated_at": "2026-07-20T08:00:00Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"total": 50,
|
||||
"total_pages": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Get Product
|
||||
|
||||
**Endpoint:** `GET /products/{id}`
|
||||
|
||||
**Description:** Get product by ID
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": "dd0e8400-e29b-41d4-a716-446655440000",
|
||||
"sku": "PRD-001",
|
||||
"barcode": "1234567890123",
|
||||
"name": "Product A",
|
||||
"description": "Description of product A",
|
||||
"category_id": "ee0e8400-e29b-41d4-a716-446655440000",
|
||||
"category": {
|
||||
"id": "ee0e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Electronics"
|
||||
},
|
||||
"unit": "pcs",
|
||||
"purchase_price": 40000,
|
||||
"selling_price": 50000,
|
||||
"stock": 100,
|
||||
"min_stock": 10,
|
||||
"max_stock": 500,
|
||||
"location": "Rak A-1",
|
||||
"status": "active",
|
||||
"images": [
|
||||
"https://cdn.profitra.id/products/product-a-1.jpg",
|
||||
"https://cdn.profitra.id/products/product-a-2.jpg"
|
||||
],
|
||||
"variants": [
|
||||
{
|
||||
"id": "ff0e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Size M",
|
||||
"sku": "PRD-001-M",
|
||||
"price": 50000,
|
||||
"stock": 50
|
||||
}
|
||||
],
|
||||
"created_at": "2026-07-20T08:00:00Z",
|
||||
"updated_at": "2026-07-20T08:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Create Product
|
||||
|
||||
**Endpoint:** `POST /products`
|
||||
|
||||
**Description:** Create new product
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Permission Required:** `products.create`
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"sku": "PRD-002",
|
||||
"barcode": "1234567890124",
|
||||
"name": "Product B",
|
||||
"description": "Description of product B",
|
||||
"category_id": "ee0e8400-e29b-41d4-a716-446655440000",
|
||||
"unit": "pcs",
|
||||
"purchase_price": 60000,
|
||||
"selling_price": 75000,
|
||||
"stock": 50,
|
||||
"min_stock": 5,
|
||||
"max_stock": 200,
|
||||
"location": "Rak B-1",
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (201):**
|
||||
```json
|
||||
{
|
||||
"id": "110e8400-e29b-41d4-a716-446655440000",
|
||||
"sku": "PRD-002",
|
||||
"name": "Product B",
|
||||
"selling_price": 75000,
|
||||
"stock": 50,
|
||||
"created_at": "2026-07-20T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (403) - Limit Reached:**
|
||||
```json
|
||||
{
|
||||
"message": "Product limit reached (100). Please upgrade your plan."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Update Product
|
||||
|
||||
**Endpoint:** `PUT /products/{id}`
|
||||
|
||||
**Description:** Update product
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Permission Required:** `products.edit`
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "Product B Updated",
|
||||
"selling_price": 80000,
|
||||
"stock": 45
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": "110e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Product B Updated",
|
||||
"selling_price": 80000,
|
||||
"stock": 45,
|
||||
"updated_at": "2026-07-20T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Delete Product
|
||||
|
||||
**Endpoint:** `DELETE /products/{id}`
|
||||
|
||||
**Description:** Soft delete product
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer {token}
|
||||
```
|
||||
|
||||
**Permission Required:** `products.delete`
|
||||
|
||||
**Success Response (204):**
|
||||
```
|
||||
No Content
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Response Format Standards
|
||||
|
||||
### Success Response
|
||||
|
||||
**Single Resource:**
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"field": "value"
|
||||
}
|
||||
```
|
||||
|
||||
**List Resources:**
|
||||
```json
|
||||
{
|
||||
"data": [...],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"total": 100,
|
||||
"total_pages": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Operation Success:**
|
||||
```json
|
||||
{
|
||||
"message": "Operation completed successfully"
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
**400 Bad Request:**
|
||||
```json
|
||||
{
|
||||
"message": "Validation errors",
|
||||
"errors": [
|
||||
{
|
||||
"field": "email",
|
||||
"message": "Invalid email format",
|
||||
"code": 1001
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized:**
|
||||
```json
|
||||
{
|
||||
"message": "Authentication credentials were missing or incorrect"
|
||||
}
|
||||
```
|
||||
|
||||
**403 Forbidden:**
|
||||
```json
|
||||
{
|
||||
"message": "You don't have permission to access this resource"
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found:**
|
||||
```json
|
||||
{
|
||||
"message": "Resource not found"
|
||||
}
|
||||
```
|
||||
|
||||
**409 Conflict:**
|
||||
```json
|
||||
{
|
||||
"message": "Resource already exists"
|
||||
}
|
||||
```
|
||||
|
||||
**429 Too Many Requests:**
|
||||
```json
|
||||
{
|
||||
"message": "Rate limit exceeded. Try again later."
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error:**
|
||||
```json
|
||||
{
|
||||
"message": "An unexpected error occurred"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
**Default Limits:**
|
||||
- Anonymous: 100 requests/hour
|
||||
- Authenticated: 1000 requests/hour
|
||||
- API Key (Pro plan): 10000 requests/hour
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
X-RateLimit-Limit: 1000
|
||||
X-RateLimit-Remaining: 999
|
||||
X-RateLimit-Reset: 1626782400
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Versioning
|
||||
|
||||
API menggunakan URL versioning: `/v1`, `/v2`, etc.
|
||||
|
||||
Current version: **v1**
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-07-20
|
||||
24
docs/history/2025-07-22-google-auth.md
Normal file
24
docs/history/2025-07-22-google-auth.md
Normal file
@ -0,0 +1,24 @@
|
||||
# 2025-07-22: Google Auth - Backend
|
||||
|
||||
## Summary
|
||||
|
||||
Menambahkan endpoint `POST /v1/auth/google-login` untuk autentikasi via Google Identity Services.
|
||||
|
||||
---
|
||||
|
||||
## Changes
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `app/config.py` | Tambah `GOOGLE_CLIENT_ID: str = ""` |
|
||||
| `.env` | Tambah `GOOGLE_CLIENT_ID=xxx` |
|
||||
| `app/schemas/user.py` | Tambah `GoogleLoginRequest(credential: str)` |
|
||||
| `app/routers/auth.py` | Tambah endpoint `POST /v1/auth/google-login` + imports (`google.oauth2`, `google.auth.transport`, `random`, `string`, `datetime`) |
|
||||
| `requirements.txt` | Tambah `google-auth==2.56.2` |
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- `google-auth` diinstall ke Python environment (`pip install google-auth`)
|
||||
- Server harus di-restart setelah tambah `GOOGLE_CLIENT_ID` ke `.env` karena `Settings()` dibuat saat import
|
||||
122
docs/technical/google-auth.md
Normal file
122
docs/technical/google-auth.md
Normal file
@ -0,0 +1,122 @@
|
||||
# Google Auth - Backend
|
||||
|
||||
## Endpoint
|
||||
|
||||
```
|
||||
POST /v1/auth/google-login
|
||||
```
|
||||
|
||||
### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"credential": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
```
|
||||
|
||||
### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "bearer",
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"email": "user@gmail.com",
|
||||
"username": "user",
|
||||
"tenant_id": null,
|
||||
"status": "ACTIVE",
|
||||
"created_at": "...",
|
||||
"profile": {
|
||||
"full_name": "User Name",
|
||||
"phone": null,
|
||||
"timezone": "Asia/Jakarta"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Responses
|
||||
|
||||
| Status | Detail |
|
||||
|--------|--------|
|
||||
| 401 | `Kredensial Google tidak valid atau telah kedaluwarsa.` |
|
||||
| 401 | `Tidak dapat mengambil email dari akun Google.` |
|
||||
|
||||
---
|
||||
|
||||
## Logic
|
||||
|
||||
1. Verify JWT token dari Google via `google.oauth2.id_token.verify_oauth2_token()` dengan `GOOGLE_CLIENT_ID`
|
||||
2. Extract `email` dan `name` dari token payload
|
||||
3. Cari user by `email` di database
|
||||
4. **Jika user belum ada:**
|
||||
- Generate username dari email prefix (jika duplicate, tambah suffix angka: `user`, `user2`, `user3`...)
|
||||
- Generate random 32-char password (tidak pernah dipakai)
|
||||
- Set `email_verified_at = now` (Google sudah verifikasi)
|
||||
- Set `status = ACTIVE`
|
||||
- Buat `UserProfile` dengan `full_name` dari Google
|
||||
5. **Jika user sudah ada:**
|
||||
- Update `email_verified_at` jika belum terisi
|
||||
- Update `full_name` jika berubah
|
||||
- Create profile jika belum ada
|
||||
6. Return JWT access token + user detail
|
||||
|
||||
---
|
||||
|
||||
## Schema
|
||||
|
||||
```python
|
||||
class GoogleLoginRequest(BaseModel):
|
||||
credential: str
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Config
|
||||
|
||||
### Environment Variable
|
||||
|
||||
```env
|
||||
# api/.env
|
||||
GOOGLE_CLIENT_ID=xxx.apps.googleusercontent.com
|
||||
```
|
||||
|
||||
### Settings
|
||||
|
||||
```python
|
||||
# api/app/config.py
|
||||
class Settings(BaseSettings):
|
||||
GOOGLE_CLIENT_ID: str = ""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
```
|
||||
google-auth==2.56.2
|
||||
```
|
||||
|
||||
Ditambahkan ke `api/requirements.txt`.
|
||||
|
||||
---
|
||||
|
||||
## Files Changed
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `api/app/config.py` | Tambah `GOOGLE_CLIENT_ID: str = ""` |
|
||||
| `api/.env` | Tambah `GOOGLE_CLIENT_ID=xxx` |
|
||||
| `api/app/schemas/user.py` | Tambah `GoogleLoginRequest` schema |
|
||||
| `api/app/routers/auth.py` | Tambah endpoint `POST /v1/auth/google-login` + imports |
|
||||
| `api/requirements.txt` | Tambah `google-auth==2.56.2` |
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions
|
||||
|
||||
- **Tidak pakai kolom `google_id`** - User matching pakai `email` dari Google token, tidak perlu kolom baru di DB
|
||||
- **Password field tetap NOT NULL** - Google user dapat random password hash (tidak pernah dipakai)
|
||||
- **Auto-verify email** - `email_verified_at` langsung diisi karena Google sudah verifikasi email
|
||||
- **Status langsung ACTIVE** - Tidak perlu alur onboarding untuk Google user
|
||||
Loading…
Reference in New Issue
Block a user