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