Spaces:
Runtime error
Runtime error
docs: Introduce comprehensive technical documentation and update planning artifacts to reflect MVP completion and detailed FR implementation status.
d03d74d | # Vault - API Contracts | |
| **Generated:** 2026-02-11 | |
| **Framework:** Next.js 15 App Router | |
| --- | |
| ## API Overview | |
| Vault uses Next.js 15 App Router with two primary patterns: | |
| 1. **API Routes** - Traditional REST endpoints (`src/app/api/`) | |
| 2. **Server Actions** - Form mutations with progressive enhancement (`'use server'`) | |
| --- | |
| ## API Routes | |
| ### Authentication | |
| #### `POST /api/auth/[...all]` | |
| Better Auth handler for all authentication endpoints. | |
| **Handled by:** [`src/app/api/auth/[...all]/route.ts`](../src/app/api/auth/[...all]/route.ts) | |
| **Endpoints:** | |
| - `POST /api/auth/sign-in/email` - Email/password sign in | |
| - `POST /api/auth/sign-up/email` - Email/password registration | |
| - `GET /api/auth/session` - Get current session | |
| - `POST /api/auth/sign-out` - Sign out | |
| - `GET /api/auth/callback/google` - Google OAuth callback | |
| --- | |
| ### Inngest | |
| #### `POST /api/inngest` | |
| Inngest webhook handler for background job execution. | |
| **Handled by:** [`src/app/api/inngest/route.ts`](../src/app/api/inngest/route.ts) | |
| **Registered Functions:** | |
| - `scan-video-archive` - YouTube archive scanning | |
| - `detect-objects` - AI object detection | |
| - `match-marketplace` - Marketplace product matching | |
| - `monitor-link-health` - Link health monitoring | |
| - `checkSingleLinkHealth` - Single link check | |
| - `warmFeaturedCreatorsCache` - Cache warming | |
| - `healSocialMetadata` - Social metadata repair | |
| **Authentication:** Requires `INNGEST_SIGNING_KEY` in production. | |
| --- | |
| ### YouTube Integration | |
| #### `GET /api/youtube/connect` | |
| Initiates YouTube OAuth flow. | |
| **Handled by:** [`src/app/api/youtube/connect/route.ts`](../src/app/api/youtube/connect/route.ts) | |
| **Response:** Redirects to Google OAuth consent screen. | |
| #### `GET /api/youtube/callback` | |
| Handles YouTube OAuth callback. | |
| **Handled by:** [`src/app/api/youtube/callback/route.ts`](../src/app/api/youtube/callback/route.ts) | |
| **Query Parameters:** | |
| - `code` - OAuth authorization code | |
| - `state` - CSRF state token | |
| **Response:** Redirects to dashboard with connection status. | |
| #### `POST /api/youtube/disconnect` | |
| Disconnects YouTube channel. | |
| **Handled by:** [`src/app/api/youtube/disconnect/route.ts`](../src/app/api/youtube/disconnect/route.ts) | |
| **Response:** JSON with success status. | |
| --- | |
| ### Health Check | |
| #### `GET /api/health` | |
| Health check endpoint for monitoring. | |
| **Handled by:** [`src/app/api/health/route.ts`](../src/app/api/health/route.ts) | |
| **Response:** | |
| ```json | |
| { | |
| "status": "ok", | |
| "timestamp": "2026-02-11T20:00:00.000Z" | |
| } | |
| ``` | |
| --- | |
| ### Manual Triggers (Development) | |
| #### `POST /api/manual-trigger` | |
| Manually trigger Inngest functions (development only). | |
| **Handled by:** [`src/app/api/manual-trigger/route.ts`](../src/app/api/manual-trigger/route.ts) | |
| --- | |
| ## Server Actions | |
| Server Actions are defined with `'use server'` directive and called directly from client components. | |
| ### Discovery Actions | |
| #### `connectYouTubeAction()` | |
| Initiates YouTube connection flow. | |
| **File:** [`src/features/discovery/actions/connect-youtube.ts`](../src/features/discovery/actions/connect-youtube.ts) | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| error?: string; | |
| } | |
| ``` | |
| --- | |
| ### Vault Actions | |
| #### `redirectToMarketplace(matchId, viewerIp, userAgent, referrer)` | |
| Tracks and redirects to marketplace affiliate link. | |
| **File:** [`src/features/vault/actions/redirect-to-marketplace.ts`](../src/features/vault/actions/redirect-to-marketplace.ts) | |
| **Parameters:** | |
| - `matchId: string` - Marketplace match ID | |
| - `viewerIp?: string` - Anonymized viewer IP | |
| - `userAgent?: string` - Client user agent | |
| - `referrer?: string` - Referrer URL | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| url?: string; | |
| error?: string; | |
| } | |
| ``` | |
| #### `searchProducts(query, creatorSlug)` | |
| Search products across creator's vault. | |
| **File:** [`src/features/vault/actions/search-products.ts`](../src/features/vault/actions/search-products.ts) | |
| **Parameters:** | |
| - `query: string` - Search query | |
| - `creatorSlug: string` - Creator's URL slug | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| products?: ProductCard[]; | |
| error?: string; | |
| } | |
| ``` | |
| #### `triggerAnalysis(videoId, videoUrl)` | |
| Trigger AI analysis for a video. | |
| **File:** [`src/features/vault/actions/trigger-analysis.ts`](../src/features/vault/actions/trigger-analysis.ts) | |
| **Parameters:** | |
| - `videoId: string` - Internal video ID | |
| - `videoUrl: string` - Video URL for processing | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| error?: string; | |
| } | |
| ``` | |
| #### `quickAnalyze(videoUrl)` | |
| Quick analysis tool for arbitrary video URLs. | |
| **File:** [`src/features/vault/actions/quick-analyze.ts`](../src/features/vault/actions/quick-analyze.ts) | |
| --- | |
| ### Moderation Actions | |
| #### `approveDetection(detectionId)` | |
| Approve a detected object for vault display. | |
| **File:** [`src/features/moderation/actions/approve-detection.ts`](../src/features/moderation/actions/approve-detection.ts) | |
| **Parameters:** | |
| - `detectionId: string` - Detection ID | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| error?: string; | |
| } | |
| ``` | |
| #### `rejectDetection(detectionId)` | |
| Reject a detected object. | |
| **File:** [`src/features/moderation/actions/reject-detection.ts`](../src/features/moderation/actions/reject-detection.ts) | |
| **Parameters:** | |
| - `detectionId: string` - Detection ID | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| error?: string; | |
| } | |
| ``` | |
| #### `bulkApprove(detectionIds)` | |
| Approve multiple detections at once. | |
| **File:** [`src/features/moderation/actions/bulk-approve.ts`](../src/features/moderation/actions/bulk-approve.ts) | |
| **Parameters:** | |
| - `detectionIds: string[]` - Array of detection IDs | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| approved: number; | |
| failed: number; | |
| error?: string; | |
| } | |
| ``` | |
| #### `bulkReject(detectionIds)` | |
| Reject multiple detections at once. | |
| **File:** [`src/features/moderation/actions/bulk-reject.ts`](../src/features/moderation/actions/bulk-reject.ts) | |
| #### `editDetection(detectionId, updates)` | |
| Edit detection details (name, category, thumbnail). | |
| **File:** [`src/features/moderation/actions/edit-detection.ts`](../src/features/moderation/actions/edit-detection.ts) | |
| **Parameters:** | |
| - `detectionId: string` - Detection ID | |
| - `updates: { objectName?, category?, thumbnailUrl? }` - Fields to update | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| error?: string; | |
| } | |
| ``` | |
| #### `deleteDetection(detectionId)` | |
| Permanently delete a detection. | |
| **File:** [`src/features/moderation/actions/delete-detection.ts`](../src/features/moderation/actions/delete-detection.ts) | |
| #### `addDetection(videoId, data)` | |
| Manually add a product detection to a video. | |
| **File:** [`src/features/moderation/actions/add-detection.ts`](../src/features/moderation/actions/add-detection.ts) | |
| **Parameters:** | |
| - `videoId: string` - Video ID | |
| - `data: { objectName, category, frameTimestamp?, thumbnailUrl?, marketplaceMatches? }` | |
| --- | |
| ### Admin Moderation Actions | |
| #### `adminCorrectDetection(detectionId, corrections)` | |
| Admin correction with audit trail. | |
| **File:** [`src/features/moderation/actions/admin-correct-detection.ts`](../src/features/moderation/actions/admin-correct-detection.ts) | |
| **Parameters:** | |
| - `detectionId: string` - Detection ID | |
| - `corrections: { objectName?, category? }` - Corrected values | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| error?: string; | |
| } | |
| ``` | |
| #### `adminMarkIncorrect(detectionId, reasonCode)` | |
| Mark detection as incorrect with reason. | |
| **File:** [`src/features/moderation/actions/admin-mark-incorrect.ts`](../src/features/moderation/actions/admin-mark-incorrect.ts) | |
| **Parameters:** | |
| - `detectionId: string` - Detection ID | |
| - `reasonCode: 'wrong_object' | 'wrong_category' | 'false_positive' | 'unclear_image' | 'duplicate' | 'out_of_scope'` | |
| --- | |
| ### Marketplace Actions | |
| #### `triggerMarketplaceMatch(objectId)` | |
| Trigger marketplace matching for a detection. | |
| **File:** [`src/features/marketplace/actions/trigger-marketplace-match.ts`](../src/features/marketplace/actions/trigger-marketplace-match.ts) | |
| **Parameters:** | |
| - `objectId: string` - Detection ID | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| matchCount?: number; | |
| error?: string; | |
| } | |
| ``` | |
| #### `triggerLinkHealthCheck(matchId)` | |
| Check health of a specific affiliate link. | |
| **File:** [`src/features/marketplace/actions/trigger-link-health-check.ts`](../src/features/marketplace/actions/trigger-link-health-check.ts) | |
| --- | |
| ### Interest Actions | |
| #### `createInterestPledge(marketplaceMatchId, email, detectedObjectId)` | |
| Create an interest pledge for out-of-stock item. | |
| **File:** [`src/features/interest/actions/create-interest-pledge.ts`](../src/features/interest/actions/create-interest-pledge.ts) | |
| **Parameters:** | |
| - `marketplaceMatchId: string | null` - Match ID | |
| - `email: string` - User email | |
| - `detectedObjectId: string | null` - Detection ID | |
| **Returns:** | |
| ```typescript | |
| { | |
| success: boolean; | |
| isDuplicate?: boolean; | |
| error?: string; | |
| } | |
| ``` | |
| --- | |
| ### Request & Proposal Actions | |
| #### `submitRequest(videoId, data)` | |
| Submit a product request for a video. | |
| **File:** [`src/actions/submit-request.ts`](../src/actions/submit-request.ts) | |
| **Parameters:** | |
| - `videoId: string` - Video ID | |
| - `data: { viewerName?, viewerEmail?, note, imageUrl?, frameTimestamp? }` | |
| #### `submitProposal(videoId, data)` | |
| Submit an affiliate link proposal. | |
| **File:** [`src/actions/submit-proposal.ts`](../src/actions/submit-proposal.ts) | |
| **Parameters:** | |
| - `videoId: string` - Video ID | |
| - `data: { productUrl, affiliateUrl, productName, price?, imageUrl?, note?, objectId? }` | |
| #### `handleProposal(proposalId, action)` | |
| Approve or reject a proposal. | |
| **File:** [`src/features/moderation/actions/handle-proposal.ts`](../src/features/moderation/actions/handle-proposal.ts) | |
| **Parameters:** | |
| - `proposalId: string` - Proposal ID | |
| - `action: 'approve' | 'reject'` - Action to take | |
| --- | |
| ### Claim Actions | |
| #### `claimVideo(videoId, creatorId)` | |
| Claim ownership of a video. | |
| **File:** [`src/actions/claim-video.ts`](../src/actions/claim-video.ts) | |
| --- | |
| ## Data Fetching Patterns | |
| ### Server-Side Rendering (SSR) | |
| Pages fetch data directly in async server components: | |
| ```typescript | |
| // src/app/vault/[creatorSlug]/page.tsx | |
| export default async function VaultPage({ params }) { | |
| const vault = await VaultService.getCreatorVault(params.creatorSlug); | |
| return <VaultGrid vault={vault} />; | |
| } | |
| ``` | |
| ### Incremental Static Regeneration (ISR) | |
| Pages can use ISR for caching: | |
| ```typescript | |
| export const revalidate = 300; // 5 minutes | |
| ``` | |
| ### Client-Side Fetching | |
| Client components use SWR or direct server action calls: | |
| ```typescript | |
| // Using server action | |
| const result = await approveDetection(detectionId); | |
| if (result.success) { | |
| toast.success('Detection approved'); | |
| } | |
| ``` | |
| --- | |
| ## Error Handling | |
| All server actions follow a consistent error pattern: | |
| ```typescript | |
| { | |
| success: boolean; | |
| error?: string; | |
| // Additional fields as needed | |
| } | |
| ``` | |
| Errors are captured with Sentry: | |
| ```typescript | |
| import * as Sentry from '@sentry/nextjs'; | |
| catch (error) { | |
| Sentry.captureException(error, { | |
| tags: { action: 'action-name', resource_id: id }, | |
| }); | |
| return { success: false, error: 'Failed to perform action' }; | |
| } | |
| ``` | |
| --- | |
| ## Authentication & Authorization | |
| ### Session Access | |
| ```typescript | |
| import { auth } from '@/lib/auth'; | |
| import { headers } from 'next/headers'; | |
| const session = await auth.api.getSession({ | |
| headers: await headers(), | |
| }); | |
| ``` | |
| ### Authorization Pattern | |
| ```typescript | |
| // Verify ownership | |
| const detection = await db.query.detectedObjects.findFirst({ | |
| where: eq(detectedObjects.id, detectionId), | |
| }); | |
| if (detection.creatorId !== session.user.id) { | |
| return { success: false, error: 'Unauthorized' }; | |
| } | |
| ``` | |
| ### Admin Check | |
| ```typescript | |
| const adminEmails = process.env.ADMIN_EMAILS?.split(',') || []; | |
| const isAdmin = session.user.email && adminEmails.includes(session.user.email); | |
| ``` | |
| --- | |
| ## Related Documentation | |
| - [Data Models](./data-models.md) - Database schema | |
| - [Inngest Workflows](./inngest-workflows.md) - Background jobs | |
| - [Development Guide](./development-guide.md) - Setup instructions | |