tenantpilot/specs/001-global-policy-search/contracts/server-actions.md
2025-12-05 22:06:22 +01:00

207 lines
4.4 KiB
Markdown

# 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<SearchResult>;
```
### 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 (
<form action={handleSearch}>
<input name="query" placeholder="Search settings..." />
<button type="submit" disabled={isPending}>
{isPending ? 'Searching...' : 'Search'}
</button>
</form>
);
}
```
---
## Action: getPolicySettingById
**File**: `lib/actions/policySettings.ts`
### Signature
```typescript
'use server';
export async function getPolicySettingById(
id: string
): Promise<GetSettingResult>;
```
### 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<RecentSettingsResult>;
```
### 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,
};
```