# VibeBackr Frontend & UI Integration Guide

## 1. Overview
VibeBackr serves as the instant backend for UI applications (React, Next.js, Vue, Svelte, HTML/JS).
- **Base URL**: `http://localhost:8080` (or `window.location.origin` in production)
- **Data Model**: Collections contain universal Records. Records store `title`, `content` (Markdown/text), and an arbitrary JSON `data` payload.
- **Permissions**: Automatic 5-tier waterfall ACLs based on caller's identity (Owner -> Manager Hierarchy -> Public -> Shared -> Admin).

## 2. Authentication for Frontends
Every API request requires an `Authorization: Bearer <token>` header.

### Frontend SSO Login (Google / OIDC)
Redirect the user to the login endpoint passing your frontend origin as `redirect_uri`:
```typescript
// 1. Redirect to Google login:
const loginUrl = '/auth/login/google?redirect_uri=' + encodeURIComponent(window.location.origin);
window.location.href = loginUrl;

// 2. On return, extract the token from URL hash:
if (window.location.hash.includes('access_token=')) {
  const params = new URLSearchParams(window.location.hash.substring(1));
  const token = params.get('access_token');
  if (token) {
    localStorage.setItem('vb_token', token);
    window.location.hash = ''; // Clean up URL
  }
}
```

### Supported SSO Providers
Redirect user to `/auth/login/google`, `/auth/login/github`, or `/auth/login/oidc` with `?redirect_uri=...`.
The server redirects back to your frontend with `#access_token=...`.

### Authenticated Request Pattern
```typescript
const token = localStorage.getItem('vb_token');
const res = await fetch('http://localhost:8080/api/collections', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
});
```

## 3. Core REST Endpoints

### Collections
- `GET /api/collections` -> Returns list of accessible collections `Collection[]`

### Universal Records CRUD
- `GET /api/collections/{collection_id}/records`
  - Returns list of records visible to the current authenticated user.
- `POST /api/collections/{collection_id}/records`
  - Request payload:
    ```json
    {
      "title": "Example Title",
      "content": "Optional description or markdown notes",
      "data": { "anyKey": "anyValue", "status": "active" },
      "access_policy": {
        "visibility": "public",
        "shares": ["teammate@company.com"]
      }
    }
    ```
- `GET /api/collections/{collection_id}/records/{record_id}` -> Returns single record.
- `PUT /api/collections/{collection_id}/records/{record_id}` -> Updates title, content, data, or access_policy.
- `DELETE /api/collections/{collection_id}/records/{record_id}` -> Deletes record (returns `{ "message": "record deleted" }`).

## 4. Search & RAG
- `POST /api/collections/{collection_id}/search`
  - Hybrid vector search (EmbeddingGemma 2) with automatic SQL text search fallback.
  - Request payload:
    ```json
    {
      "query": "search query string",
      "limit": 20
    }
    ```
  - Returns array of `{ "record": Record, "score": float }`.

## 5. Drop-in TypeScript Client (Copy & Paste into UI codebase)
Save this as `src/lib/vibeBackr.ts` in your frontend project:

```typescript
export interface VibeRecord<T = Record<string, any>> {
  id: string;
  collection_id: string;
  title: string;
  content: string;
  data: string; // JSON string payload; parse with JSON.parse()
  created_by: string;
  created_at: string;
  updated_at: string;
  access_policy: {
    visibility: 'private' | 'public' | 'shared';
    shares?: string[];
  };
}

export function createVibeClient(baseUrl = 'http://localhost:8080') {
  const getToken = () => (typeof window !== 'undefined' ? localStorage.getItem('vb_token') : null);
  const setToken = (t: string) => (typeof window !== 'undefined' ? localStorage.setItem('vb_token', t) : null);

  async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
    const token = getToken();
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      ...(token ? { Authorization: 'Bearer ' + token } : {}),
      ...(options.headers as any),
    };
    const res = await fetch(baseUrl + path, { ...options, headers });
    if (!res.ok) {
      const err = await res.json().catch(() => ({ error: res.statusText }));
      throw new Error(err.error || ('Request failed: ' + res.status));
    }
    return res.json();
  }

  return {
    getToken,
    setToken,
    getLoginUrl: (provider = 'google', redirectUri = window.location.origin) =>
      '/auth/login/' + provider + '?redirect_uri=' + encodeURIComponent(redirectUri),
    listCollections: () => request<any[]>('/api/collections'),
    listRecords: <T = any>(colId: string) => request<VibeRecord<T>[]>('/api/collections/' + colId + '/records'),
    getRecord: <T = any>(colId: string, id: string) => request<VibeRecord<T>>('/api/collections/' + colId + '/records/' + id),
    createRecord: <T = any>(colId: string, payload: { title: string; content?: string; data?: T; access_policy?: any }) =>
      request<VibeRecord<T>>('/api/collections/' + colId + '/records', { method: 'POST', body: JSON.stringify(payload) }),
    updateRecord: <T = any>(colId: string, id: string, payload: { title?: string; content?: string; data?: T; access_policy?: any }) =>
      request<VibeRecord<T>>('/api/collections/' + colId + '/records/' + id, { method: 'PUT', body: JSON.stringify(payload) }),
    deleteRecord: (colId: string, id: string) =>
      request<{ message: string }>('/api/collections/' + colId + '/records/' + id, { method: 'DELETE' }),
    searchRecords: <T = any>(colId: string, query: string, limit = 20) =>
      request<{ record: VibeRecord<T>; score: number }[]>('/api/collections/' + colId + '/search', { method: 'POST', body: JSON.stringify({ query, limit }) }),
  };
}
```

