# Server Actions Contract: Policy Search **Feature**: 001-global-policy-search **Type**: Next.js Server Actions --- ## Overview Server Actions for the Global Policy Search feature. These actions are called directly from React Server Components and Client Components without API routes. --- ## Action: searchPolicySettings **File**: `lib/actions/policySettings.ts` ### Signature ```typescript 'use server'; export async function searchPolicySettings( searchTerm: string ): Promise; ``` ### Input | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `searchTerm` | string | Yes | Search query (min 2 chars) | ### Output ```typescript interface SearchResult { success: boolean; data?: PolicySettingSearchResult[]; error?: string; totalCount?: number; } interface PolicySettingSearchResult { id: string; policyName: string; policyType: string; settingName: string; settingValue: string; lastSyncedAt: Date; } ``` ### Behavior 1. **Authentication**: Validates user session via `getUserAuth()` 2. **Tenant Isolation**: Extracts `tenantId` from session, filters all queries 3. **Search**: Case-insensitive search on `settingName` and `settingValue` 4. **Limit**: Returns max 100 results, sorted by `settingName` ### Error Responses | Condition | Response | |-----------|----------| | Not authenticated | `{ success: false, error: 'Unauthorized' }` | | Search term < 2 chars | `{ success: false, error: 'Search term too short' }` | | No tenant ID in session | `{ success: false, error: 'Tenant not found' }` | | Database error | `{ success: false, error: 'Search failed' }` | ### Example Usage (Client Component) ```typescript 'use client'; import { searchPolicySettings } from '@/lib/actions/policySettings'; import { useState, useTransition } from 'react'; function SearchForm() { const [results, setResults] = useState([]); const [isPending, startTransition] = useTransition(); const handleSearch = (formData: FormData) => { const query = formData.get('query') as string; startTransition(async () => { const result = await searchPolicySettings(query); if (result.success) { setResults(result.data ?? []); } }); }; return (
); } ``` --- ## Action: getPolicySettingById **File**: `lib/actions/policySettings.ts` ### Signature ```typescript 'use server'; export async function getPolicySettingById( id: string ): Promise; ``` ### Input | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `id` | string | Yes | Policy setting ID (CUID2) | ### Output ```typescript interface GetSettingResult { success: boolean; data?: PolicySetting; error?: string; } ``` ### Behavior 1. **Authentication**: Validates user session 2. **Tenant Isolation**: Ensures setting belongs to user's tenant 3. **Return**: Full policy setting record or null --- ## Action: getRecentPolicySettings **File**: `lib/actions/policySettings.ts` ### Signature ```typescript 'use server'; export async function getRecentPolicySettings( limit?: number ): Promise; ``` ### Input | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `limit` | number | No | 20 | Max results (1-100) | ### Output ```typescript interface RecentSettingsResult { success: boolean; data?: PolicySettingSearchResult[]; error?: string; } ``` ### Behavior 1. **Authentication**: Validates user session 2. **Tenant Isolation**: Filters by `tenantId` 3. **Sort**: By `lastSyncedAt` descending 4. **Limit**: Capped at 100 --- ## Security Invariants All Server Actions MUST: 1. ✅ Call `getUserAuth()` at the start 2. ✅ Return `{ success: false, error: 'Unauthorized' }` if no session 3. ✅ Extract `tenantId` from session 4. ✅ Include `tenantId` in ALL database queries (WHERE clause) 5. ✅ Never expose settings from other tenants 6. ✅ Validate and sanitize all input parameters --- ## Type Exports ```typescript // lib/actions/policySettings.ts export type { SearchResult, GetSettingResult, RecentSettingsResult, PolicySettingSearchResult, }; ```