207 lines
4.4 KiB
Markdown
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,
|
|
};
|
|
```
|