From 579f8a3f21f009679fb6bad4bfd102be59f84f02 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 22 Jul 2026 16:50:20 +0700 Subject: [PATCH] Add API documentation for Google authentication endpoint --- API_DOCUMENTATION.md | 946 +++++++++++++++++++++++++ docs/history/2025-07-22-google-auth.md | 24 + docs/technical/google-auth.md | 122 ++++ 3 files changed, 1092 insertions(+) create mode 100644 API_DOCUMENTATION.md create mode 100644 docs/history/2025-07-22-google-auth.md create mode 100644 docs/technical/google-auth.md diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md new file mode 100644 index 0000000..ecc1dc5 --- /dev/null +++ b/API_DOCUMENTATION.md @@ -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 diff --git a/docs/history/2025-07-22-google-auth.md b/docs/history/2025-07-22-google-auth.md new file mode 100644 index 0000000..79bbbf3 --- /dev/null +++ b/docs/history/2025-07-22-google-auth.md @@ -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 diff --git a/docs/technical/google-auth.md b/docs/technical/google-auth.md new file mode 100644 index 0000000..c9ec2fa --- /dev/null +++ b/docs/technical/google-auth.md @@ -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