فهرست منبع

docs: add unified API response format documentation #53

Claude 5 ماه پیش
والد
کامیت
56bbd16445
1فایلهای تغییر یافته به همراه409 افزوده شده و 0 حذف شده
  1. 409 0
      UNIFIED_API_FORMAT.md

+ 409 - 0
UNIFIED_API_FORMAT.md

@@ -0,0 +1,409 @@
+# Unified Webshop API Response Format
+
+**Related:** Issue #53 - Unified webshop API response format
+
+## Overview
+
+All Shop Data API endpoints now return responses in a **consistent, unified format** regardless of the e-commerce platform (Shopify, WooCommerce, or ShopRenter). This standardization makes it easier to build platform-agnostic applications and handle responses uniformly.
+
+## Response Structure
+
+### Base Response Interface
+
+```typescript
+interface UnifiedApiResponse<T> {
+  success: boolean;              // Request success status
+  data: T | T[];                 // Response data (single item or array)
+  metadata: ResponseMetadata;    // Request metadata
+  pagination?: Pagination;       // Only for list endpoints
+  error?: ErrorDetails;          // Only when success=false
+}
+```
+
+### Metadata
+
+Every response includes comprehensive metadata:
+
+```typescript
+interface ResponseMetadata {
+  platform?: string;           // "shopify" | "woocommerce" | "shoprenter"
+  store_id?: string;          // Store UUID (for data endpoints)
+  resource_type: string;      // "customers" | "orders" | "products" | "stores"
+  auth_type: "user" | "internal";  // Authentication type used
+  fetched_at: string;         // ISO 8601 timestamp
+  request_id?: string;        // Unique request tracking ID
+}
+```
+
+### Pagination (List Endpoints)
+
+Paginated responses include:
+
+```typescript
+interface Pagination {
+  page: number;              // Current page (1-indexed)
+  limit: number;             // Items per page
+  total?: number;            // Total items (if available)
+  has_more: boolean;         // Whether more pages exist
+  next_page: number | null;  // Next page number or null
+  prev_page: number | null;  // Previous page number or null
+}
+```
+
+### Error Response
+
+Failed requests return:
+
+```typescript
+interface ErrorDetails {
+  code: string;        // Error code for programmatic handling
+  message: string;     // Human-readable error message
+  details?: unknown;   // Additional error context
+}
+```
+
+## Response Examples
+
+### List Response (Paginated)
+
+**Request:**
+```bash
+GET /shop-data-api/products?store_id={uuid}&page=1&limit=25
+Authorization: Bearer int_shopcall_xxx
+```
+
+**Response:**
+```json
+{
+  "success": true,
+  "data": [
+    {
+      "id": "7654321",
+      "platform": "shopify",
+      "title": "Awesome Product",
+      "price": 29.99,
+      "currency": "USD",
+      "status": "active",
+      ...
+    },
+    {
+      "id": "7654322",
+      "platform": "shopify",
+      "title": "Another Product",
+      "price": 49.99,
+      "currency": "USD",
+      "status": "active",
+      ...
+    }
+  ],
+  "metadata": {
+    "platform": "shopify",
+    "store_id": "73ca58c0-e47f-4caa-bcdb-2d0b1fda27ce",
+    "resource_type": "products",
+    "auth_type": "internal",
+    "fetched_at": "2025-11-01T13:45:30.123Z",
+    "request_id": "req_1730462730_a8f3c9d2"
+  },
+  "pagination": {
+    "page": 1,
+    "limit": 25,
+    "has_more": true,
+    "next_page": 2,
+    "prev_page": null
+  }
+}
+```
+
+### Single Item Response
+
+**Request:**
+```bash
+GET /shop-data-api/orders/12345?store_id={uuid}
+Authorization: Bearer api_shopcall_xxx
+```
+
+**Response:**
+```json
+{
+  "success": true,
+  "data": {
+    "id": "12345",
+    "platform": "woocommerce",
+    "order_number": "1001",
+    "status": "completed",
+    "total_price": 129.99,
+    "currency": "USD",
+    "customer_name": "John Doe",
+    "customer_email": "john@example.com",
+    ...
+  },
+  "metadata": {
+    "platform": "woocommerce",
+    "store_id": "73ca58c0-e47f-4caa-bcdb-2d0b1fda27ce",
+    "resource_type": "orders",
+    "auth_type": "user",
+    "fetched_at": "2025-11-01T13:45:30.123Z",
+    "request_id": "req_1730462730_b9e4d1c3"
+  }
+}
+```
+
+### Error Response
+
+**Request:**
+```bash
+GET /shop-data-api/customers?store_id=invalid-uuid
+Authorization: Bearer int_shopcall_xxx
+```
+
+**Response (404):**
+```json
+{
+  "success": false,
+  "data": [],
+  "metadata": {
+    "resource_type": "customers",
+    "auth_type": "internal",
+    "fetched_at": "2025-11-01T13:45:30.123Z"
+  },
+  "error": {
+    "code": "STORE_NOT_FOUND",
+    "message": "Store not found or access denied"
+  }
+}
+```
+
+### Stores List Response
+
+**Request:**
+```bash
+GET /shop-data-api/stores
+Authorization: Bearer int_shopcall_xxx
+```
+
+**Response:**
+```json
+{
+  "success": true,
+  "data": [
+    {
+      "id": "uuid-1",
+      "store_name": "My Shopify Store",
+      "store_url": "mystore.myshopify.com",
+      "platform_name": "shopify",
+      "is_active": true,
+      "connected_at": "2025-10-01T10:00:00Z",
+      "sync_status": "completed"
+    },
+    {
+      "id": "uuid-2",
+      "store_name": "WooCommerce Shop",
+      "store_url": "woo.example.com",
+      "platform_name": "woocommerce",
+      "is_active": true,
+      "connected_at": "2025-10-15T14:30:00Z",
+      "sync_status": "idle"
+    }
+  ],
+  "metadata": {
+    "resource_type": "stores",
+    "auth_type": "internal",
+    "fetched_at": "2025-11-01T13:45:30.123Z",
+    "request_id": "req_1730462730_c7f5e2d4"
+  },
+  "pagination": {
+    "page": 1,
+    "limit": 2,
+    "total": 2,
+    "has_more": false,
+    "next_page": null,
+    "prev_page": null
+  }
+}
+```
+
+## Error Codes
+
+Common error codes returned by the API:
+
+| Code | HTTP Status | Description |
+|------|-------------|-------------|
+| `METHOD_NOT_ALLOWED` | 405 | Only GET requests are supported |
+| `INVALID_ENDPOINT` | 404 | Invalid API endpoint |
+| `MISSING_STORE_ID` | 400 | Required parameter `store_id` not provided |
+| `INVALID_RESOURCE` | 400 | Invalid resource type (must be customers, orders, products, or stores) |
+| `INVALID_PAGINATION` | 400 | Invalid pagination parameters |
+| `STORE_NOT_FOUND` | 404 | Store not found or access denied |
+| `ACCESS_DENIED` | 403 | User does not have permission to access this resource |
+| `UNSUPPORTED_PLATFORM` | 400 | Platform is not supported |
+| `FETCH_ERROR` | 500 | Error fetching data from e-commerce platform |
+| `MISSING_API_KEY` | 401 | No API key provided in Authorization header |
+
+## Benefits of Unified Format
+
+### 1. **Platform-Agnostic Code**
+
+You can write the same code to handle responses from any platform:
+
+```typescript
+async function fetchProducts(storeId: string) {
+  const response = await fetch(
+    `/shop-data-api/products?store_id=${storeId}`,
+    { headers: { Authorization: `Bearer ${apiKey}` } }
+  );
+
+  const result = await response.json();
+
+  // Same handling for Shopify, WooCommerce, ShopRenter
+  if (result.success) {
+    return result.data.map(product => ({
+      id: product.id,
+      name: product.title,
+      price: product.price,
+      platform: result.metadata.platform  // Track which platform
+    }));
+  } else {
+    throw new Error(result.error.message);
+  }
+}
+```
+
+### 2. **Consistent Error Handling**
+
+```typescript
+function handleApiError(response: UnifiedApiResponse<any>) {
+  if (!response.success && response.error) {
+    switch (response.error.code) {
+      case 'STORE_NOT_FOUND':
+        alert('Store not found');
+        break;
+      case 'ACCESS_DENIED':
+        alert('You do not have permission to access this resource');
+        break;
+      default:
+        alert(`Error: ${response.error.message}`);
+    }
+  }
+}
+```
+
+### 3. **Uniform Pagination**
+
+```typescript
+function renderPagination(pagination: Pagination) {
+  return (
+    <div>
+      {pagination.prev_page && (
+        <button onClick={() => loadPage(pagination.prev_page)}>
+          Previous
+        </button>
+      )}
+      <span>Page {pagination.page}</span>
+      {pagination.next_page && (
+        <button onClick={() => loadPage(pagination.next_page)}>
+          Next
+        </button>
+      )}
+    </div>
+  );
+}
+```
+
+### 4. **Request Tracking**
+
+```typescript
+// Track requests for debugging
+console.log(`Request ID: ${response.metadata.request_id}`);
+console.log(`Fetched at: ${response.metadata.fetched_at}`);
+console.log(`Platform: ${response.metadata.platform}`);
+console.log(`Auth type: ${response.metadata.auth_type}`);
+```
+
+## Available Endpoints
+
+All endpoints use the unified response format:
+
+- `GET /shop-data-api/stores` - List all stores
+- `GET /shop-data-api/products?store_id={uuid}` - List products
+- `GET /shop-data-api/products/{id}?store_id={uuid}` - Get single product
+- `GET /shop-data-api/orders?store_id={uuid}` - List orders
+- `GET /shop-data-api/orders/{id}?store_id={uuid}` - Get single order
+- `GET /shop-data-api/customers?store_id={uuid}` - List customers
+- `GET /shop-data-api/customers/{id}?store_id={uuid}` - Get single customer
+
+### Query Parameters
+
+- `store_id` (required for data endpoints) - Store UUID
+- `page` (optional, default: 1) - Page number (1-indexed)
+- `limit` (optional, default: 25, max: 100) - Items per page
+- `status` (optional, orders only) - Filter by order status
+
+## Implementation Details
+
+### Files
+
+- **Response types:** `supabase/functions/_shared/unified-response.ts`
+- **API implementation:** `supabase/functions/shop-data-api/index.ts`
+- **Platform adapters:** `supabase/functions/_shared/platform-adapters.ts`
+
+### Helper Functions
+
+```typescript
+// Create success response for single item
+createSuccessResponse(data, metadata)
+
+// Create list response with pagination
+createListResponse(data, metadata, pagination)
+
+// Create error response
+createErrorResponse(code, message, details?, status?)
+
+// Convert to HTTP Response
+toHttpResponse(response, status?)
+
+// Calculate has_more flag
+calculateHasMore(returnedCount, requestedLimit)
+
+// Generate unique request ID
+generateRequestId()
+```
+
+## Migration Guide
+
+If you have existing code that relies on the old response format:
+
+### Before (Old Format)
+
+```typescript
+const response = await fetch('/shop-data-api/products');
+const data = await response.json();
+
+// Different structure per platform
+if (data.success) {
+  const products = data.data;
+  const page = data.pagination?.page;
+  const platform = data.platform;  // May or may not exist
+}
+```
+
+### After (Unified Format)
+
+```typescript
+const response = await fetch('/shop-data-api/products?store_id=uuid');
+const data = await response.json();
+
+// Same structure for all platforms
+if (data.success) {
+  const products = data.data;
+  const page = data.pagination.page;
+  const platform = data.metadata.platform;  // Always exists
+  const requestId = data.metadata.request_id;  // New: track requests
+}
+```
+
+## See Also
+
+- [Internal API Keys Documentation](INTERNAL_API_KEYS.md)
+- [Platform Adapters](supabase/functions/_shared/platform-adapters.ts)
+- Issue #50 - Non user-based API keys
+- Issue #53 - Unified webshop API