All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
1582 lines
32 KiB
Markdown
1582 lines
32 KiB
Markdown
# Knowledge Module - API Documentation
|
|
|
|
Complete REST API reference for the Knowledge Module endpoints.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Authentication](#authentication)
|
|
2. [Entry Endpoints](#entry-endpoints)
|
|
3. [Search Endpoints](#search-endpoints)
|
|
4. [Tag Endpoints](#tag-endpoints)
|
|
5. [Import/Export Endpoints](#importexport-endpoints)
|
|
6. [Stats Endpoints](#stats-endpoints)
|
|
7. [Cache Endpoints](#cache-endpoints)
|
|
8. [Error Responses](#error-responses)
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
All Knowledge Module endpoints require authentication via Bearer token and workspace context.
|
|
|
|
### Headers
|
|
|
|
```http
|
|
Authorization: Bearer {session_token}
|
|
x-workspace-id: {workspace_uuid}
|
|
```
|
|
|
|
### Permission Levels
|
|
|
|
- **WORKSPACE_ANY**: Any workspace member (including GUEST)
|
|
- **WORKSPACE_MEMBER**: MEMBER role or higher
|
|
- **WORKSPACE_ADMIN**: ADMIN or OWNER role
|
|
|
|
---
|
|
|
|
## Entry Endpoints
|
|
|
|
### List Entries
|
|
|
|
Get a paginated list of knowledge entries.
|
|
|
|
```http
|
|
GET /api/knowledge/entries
|
|
```
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Default | Description |
|
|
| --------- | ------- | ------- | --------------------------------------------- |
|
|
| `page` | integer | 1 | Page number |
|
|
| `limit` | integer | 20 | Results per page (max: 100) |
|
|
| `status` | string | - | Filter by status (DRAFT, PUBLISHED, ARCHIVED) |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/entries?page=1&limit=20&status=PUBLISHED' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"slug": "react-hooks-guide",
|
|
"title": "React Hooks Guide",
|
|
"content": "# React Hooks Guide\n\nComprehensive guide...",
|
|
"contentHtml": "<h1>React Hooks Guide</h1><p>Comprehensive guide...</p>",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "PUBLISHED",
|
|
"visibility": "WORKSPACE",
|
|
"createdAt": "2024-01-29T10:00:00Z",
|
|
"updatedAt": "2024-01-30T15:30:00Z",
|
|
"createdBy": "user-uuid",
|
|
"updatedBy": "user-uuid",
|
|
"tags": [
|
|
{
|
|
"id": "tag-uuid",
|
|
"name": "React",
|
|
"slug": "react",
|
|
"color": "#61dafb"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"pagination": {
|
|
"page": 1,
|
|
"limit": 20,
|
|
"total": 45,
|
|
"totalPages": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Get Entry
|
|
|
|
Retrieve a single knowledge entry by slug.
|
|
|
|
```http
|
|
GET /api/knowledge/entries/:slug
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------ | -------------------------------------- |
|
|
| `slug` | string | Entry slug (e.g., "react-hooks-guide") |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/entries/react-hooks-guide' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"slug": "react-hooks-guide",
|
|
"title": "React Hooks Guide",
|
|
"content": "# React Hooks Guide\n\nUse [[useState]] and [[useEffect]]...",
|
|
"contentHtml": "<h1>React Hooks Guide</h1><p>Use <a>useState</a>...</p>",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "PUBLISHED",
|
|
"visibility": "WORKSPACE",
|
|
"createdAt": "2024-01-29T10:00:00Z",
|
|
"updatedAt": "2024-01-30T15:30:00Z",
|
|
"createdBy": "user-uuid",
|
|
"updatedBy": "user-uuid",
|
|
"tags": [
|
|
{
|
|
"id": "tag-uuid",
|
|
"name": "React",
|
|
"slug": "react",
|
|
"color": "#61dafb"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Create Entry
|
|
|
|
Create a new knowledge entry.
|
|
|
|
```http
|
|
POST /api/knowledge/entries
|
|
```
|
|
|
|
**Permissions:** WORKSPACE_MEMBER
|
|
|
|
**Request Body:**
|
|
|
|
```json
|
|
{
|
|
"title": "React Hooks Guide",
|
|
"content": "# React Hooks Guide\n\nContent here...",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "DRAFT",
|
|
"visibility": "WORKSPACE",
|
|
"tags": ["react", "frontend"],
|
|
"changeNote": "Initial draft"
|
|
}
|
|
```
|
|
|
|
**Body Schema:**
|
|
|
|
| Field | Type | Required | Constraints |
|
|
| ------------ | -------- | -------- | --------------------------------------------- |
|
|
| `title` | string | Yes | 1-500 characters |
|
|
| `content` | string | Yes | Min 1 character |
|
|
| `summary` | string | No | Max 1000 characters |
|
|
| `status` | enum | No | DRAFT, PUBLISHED, ARCHIVED (default: DRAFT) |
|
|
| `visibility` | enum | No | PRIVATE, WORKSPACE, PUBLIC (default: PRIVATE) |
|
|
| `tags` | string[] | No | Array of tag slugs |
|
|
| `changeNote` | string | No | Max 500 characters |
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X POST 'http://localhost:3001/api/knowledge/entries' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"title": "React Hooks Guide",
|
|
"content": "# React Hooks Guide\n\nUse [[useState]] for state...",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "DRAFT",
|
|
"tags": ["react", "frontend"],
|
|
"changeNote": "Initial draft"
|
|
}'
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"slug": "react-hooks-guide",
|
|
"title": "React Hooks Guide",
|
|
"content": "# React Hooks Guide\n\nUse [[useState]] for state...",
|
|
"contentHtml": "<h1>React Hooks Guide</h1><p>Use <a>useState</a>...</p>",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "DRAFT",
|
|
"visibility": "WORKSPACE",
|
|
"createdAt": "2024-01-30T10:00:00Z",
|
|
"updatedAt": "2024-01-30T10:00:00Z",
|
|
"createdBy": "user-uuid",
|
|
"updatedBy": "user-uuid",
|
|
"tags": [
|
|
{
|
|
"id": "tag-uuid",
|
|
"name": "React",
|
|
"slug": "react",
|
|
"color": "#61dafb"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Slug is auto-generated from title
|
|
- If tags don't exist, they are created automatically
|
|
- Wiki-links in content are automatically parsed and stored
|
|
- First version (version 1) is created automatically
|
|
|
|
---
|
|
|
|
### Update Entry
|
|
|
|
Update an existing knowledge entry.
|
|
|
|
```http
|
|
PUT /api/knowledge/entries/:slug
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------ | ----------- |
|
|
| `slug` | string | Entry slug |
|
|
|
|
**Permissions:** WORKSPACE_MEMBER
|
|
|
|
**Request Body:**
|
|
|
|
```json
|
|
{
|
|
"title": "React Hooks Guide (Updated)",
|
|
"content": "# React Hooks Guide\n\nUpdated content...",
|
|
"summary": "Updated summary",
|
|
"status": "PUBLISHED",
|
|
"visibility": "WORKSPACE",
|
|
"tags": ["react", "frontend", "tutorial"],
|
|
"changeNote": "Added examples and updated title"
|
|
}
|
|
```
|
|
|
|
All fields are optional. Only provided fields are updated.
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X PUT 'http://localhost:3001/api/knowledge/entries/react-hooks-guide' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"status": "PUBLISHED",
|
|
"changeNote": "Ready for publication"
|
|
}'
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"slug": "react-hooks-guide",
|
|
"title": "React Hooks Guide (Updated)",
|
|
"content": "# React Hooks Guide\n\nUpdated content...",
|
|
"contentHtml": "<h1>React Hooks Guide</h1><p>Updated...</p>",
|
|
"summary": "Updated summary",
|
|
"status": "PUBLISHED",
|
|
"visibility": "WORKSPACE",
|
|
"createdAt": "2024-01-30T10:00:00Z",
|
|
"updatedAt": "2024-01-30T12:00:00Z",
|
|
"createdBy": "user-uuid",
|
|
"updatedBy": "user-uuid",
|
|
"tags": [...]
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- A new version is created on every update
|
|
- Wiki-links are re-parsed and synchronized
|
|
- Cache is invalidated automatically
|
|
|
|
---
|
|
|
|
### Delete Entry
|
|
|
|
Soft-delete (archive) a knowledge entry.
|
|
|
|
```http
|
|
DELETE /api/knowledge/entries/:slug
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------ | ----------- |
|
|
| `slug` | string | Entry slug |
|
|
|
|
**Permissions:** WORKSPACE_ADMIN
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X DELETE 'http://localhost:3001/api/knowledge/entries/react-hooks-guide' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"message": "Entry archived successfully"
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- This is a **soft delete** (sets status to ARCHIVED)
|
|
- Entry remains in database with all versions
|
|
- Links to this entry become unresolved
|
|
- Can be restored by updating status back to DRAFT or PUBLISHED
|
|
|
|
---
|
|
|
|
### Get Backlinks
|
|
|
|
Get all entries that link to this entry.
|
|
|
|
```http
|
|
GET /api/knowledge/entries/:slug/backlinks
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------ | ----------- |
|
|
| `slug` | string | Entry slug |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/entries/react-hooks/backlinks' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"entry": {
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"slug": "react-hooks",
|
|
"title": "React Hooks"
|
|
},
|
|
"backlinks": [
|
|
{
|
|
"id": "link-uuid-1",
|
|
"sourceId": "entry-uuid-1",
|
|
"source": {
|
|
"id": "entry-uuid-1",
|
|
"slug": "frontend-guide",
|
|
"title": "Frontend Development Guide",
|
|
"summary": "Complete frontend guide"
|
|
},
|
|
"linkText": "React Hooks",
|
|
"context": "Learn about [[React Hooks]] for state management.",
|
|
"createdAt": "2024-01-29T10:00:00Z"
|
|
},
|
|
{
|
|
"id": "link-uuid-2",
|
|
"sourceId": "entry-uuid-2",
|
|
"source": {
|
|
"id": "entry-uuid-2",
|
|
"slug": "component-patterns",
|
|
"title": "React Component Patterns",
|
|
"summary": null
|
|
},
|
|
"linkText": "hooks",
|
|
"context": "Modern [[hooks|React hooks]] make state simple.",
|
|
"createdAt": "2024-01-30T08:00:00Z"
|
|
}
|
|
],
|
|
"count": 2
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Only resolved links are included
|
|
- Context shows surrounding text (future feature)
|
|
- Sorted by creation date (newest first)
|
|
|
|
---
|
|
|
|
### List Versions
|
|
|
|
Get version history for an entry.
|
|
|
|
```http
|
|
GET /api/knowledge/entries/:slug/versions
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------ | ----------- |
|
|
| `slug` | string | Entry slug |
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Default | Description |
|
|
| --------- | ------- | ------- | --------------------------- |
|
|
| `page` | integer | 1 | Page number |
|
|
| `limit` | integer | 20 | Results per page (max: 100) |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/entries/react-hooks-guide/versions?page=1&limit=10' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"id": "version-uuid-3",
|
|
"version": 3,
|
|
"title": "React Hooks Guide",
|
|
"summary": "Learn about React Hooks",
|
|
"changeNote": "Added examples",
|
|
"createdAt": "2024-01-30T15:00:00Z",
|
|
"createdBy": "user-uuid",
|
|
"author": {
|
|
"id": "user-uuid",
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
},
|
|
{
|
|
"id": "version-uuid-2",
|
|
"version": 2,
|
|
"title": "React Hooks Guide",
|
|
"summary": "Learn about React Hooks",
|
|
"changeNote": "Fixed typos",
|
|
"createdAt": "2024-01-30T12:00:00Z",
|
|
"createdBy": "user-uuid",
|
|
"author": { ... }
|
|
},
|
|
{
|
|
"id": "version-uuid-1",
|
|
"version": 1,
|
|
"title": "React Hooks Guide",
|
|
"summary": null,
|
|
"changeNote": "Initial draft",
|
|
"createdAt": "2024-01-30T10:00:00Z",
|
|
"createdBy": "user-uuid",
|
|
"author": { ... }
|
|
}
|
|
],
|
|
"pagination": {
|
|
"page": 1,
|
|
"limit": 10,
|
|
"total": 3,
|
|
"totalPages": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Versions sorted newest first
|
|
- Content is NOT included (use Get Version endpoint for full content)
|
|
- First version has `changeNote` from creation
|
|
|
|
---
|
|
|
|
### Get Version
|
|
|
|
Get complete content for a specific version.
|
|
|
|
```http
|
|
GET /api/knowledge/entries/:slug/versions/:version
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------- | -------------- |
|
|
| `slug` | string | Entry slug |
|
|
| `version` | integer | Version number |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/entries/react-hooks-guide/versions/2' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"id": "version-uuid-2",
|
|
"entryId": "entry-uuid",
|
|
"version": 2,
|
|
"title": "React Hooks Guide",
|
|
"content": "# React Hooks Guide\n\nOld content...",
|
|
"summary": "Learn about React Hooks",
|
|
"changeNote": "Fixed typos",
|
|
"createdAt": "2024-01-30T12:00:00Z",
|
|
"createdBy": "user-uuid",
|
|
"author": {
|
|
"id": "user-uuid",
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Includes full content as it existed in that version
|
|
- Use this to preview before restoring
|
|
|
|
---
|
|
|
|
### Restore Version
|
|
|
|
Restore an entry to a previous version.
|
|
|
|
```http
|
|
POST /api/knowledge/entries/:slug/restore/:version
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------- | ------------------------- |
|
|
| `slug` | string | Entry slug |
|
|
| `version` | integer | Version number to restore |
|
|
|
|
**Permissions:** WORKSPACE_MEMBER
|
|
|
|
**Request Body:**
|
|
|
|
```json
|
|
{
|
|
"changeNote": "Restored version 2 - reverted bad changes"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Constraints |
|
|
| ------------ | ------ | -------- | ------------------ |
|
|
| `changeNote` | string | Yes | Max 500 characters |
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X POST 'http://localhost:3001/api/knowledge/entries/react-hooks-guide/restore/2' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"changeNote": "Restored version 2 - reverted bad changes"
|
|
}'
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"id": "entry-uuid",
|
|
"slug": "react-hooks-guide",
|
|
"title": "React Hooks Guide",
|
|
"content": "# React Hooks Guide\n\nRestored content...",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "PUBLISHED",
|
|
"visibility": "WORKSPACE",
|
|
"createdAt": "2024-01-30T10:00:00Z",
|
|
"updatedAt": "2024-01-30T16:00:00Z",
|
|
"createdBy": "user-uuid",
|
|
"updatedBy": "current-user-uuid",
|
|
"tags": [...]
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Creates a **new version** (e.g., version 4) with content from the specified version
|
|
- Original versions remain untouched (no history rewriting)
|
|
- Change note is required to document why you restored
|
|
- Wiki-links are re-parsed from the restored content
|
|
|
|
---
|
|
|
|
## Search Endpoints
|
|
|
|
### Full-Text Search
|
|
|
|
Search across entry titles and content.
|
|
|
|
```http
|
|
GET /api/knowledge/search
|
|
```
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Required | Default | Description |
|
|
| --------- | ------- | -------- | ------- | --------------------------- |
|
|
| `q` | string | Yes | - | Search query |
|
|
| `status` | enum | No | - | DRAFT, PUBLISHED, ARCHIVED |
|
|
| `page` | integer | No | 1 | Page number |
|
|
| `limit` | integer | No | 20 | Results per page (max: 100) |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/search?q=react+hooks&page=1&limit=10' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"id": "entry-uuid",
|
|
"slug": "react-hooks-guide",
|
|
"title": "React Hooks Guide",
|
|
"content": "# React Hooks Guide\n\nContent...",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "PUBLISHED",
|
|
"visibility": "WORKSPACE",
|
|
"createdAt": "2024-01-30T10:00:00Z",
|
|
"updatedAt": "2024-01-30T12:00:00Z",
|
|
"createdBy": "user-uuid",
|
|
"updatedBy": "user-uuid",
|
|
"tags": [...]
|
|
}
|
|
],
|
|
"pagination": {
|
|
"page": 1,
|
|
"limit": 10,
|
|
"total": 5,
|
|
"totalPages": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
**Search behavior:**
|
|
|
|
- Searches both `title` and `content` fields
|
|
- Case-insensitive
|
|
- Partial word matching
|
|
- Relevance-ranked results
|
|
|
|
---
|
|
|
|
### Search by Tags
|
|
|
|
Find entries with specific tags.
|
|
|
|
```http
|
|
GET /api/knowledge/search/by-tags
|
|
```
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Required | Default | Description |
|
|
| --------- | ------- | -------- | ------- | --------------------------- |
|
|
| `tags` | string | Yes | - | Comma-separated tag slugs |
|
|
| `status` | enum | No | - | DRAFT, PUBLISHED, ARCHIVED |
|
|
| `page` | integer | No | 1 | Page number |
|
|
| `limit` | integer | No | 20 | Results per page (max: 100) |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/search/by-tags?tags=react,frontend&status=PUBLISHED' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
Same format as Full-Text Search response.
|
|
|
|
**Notes:**
|
|
|
|
- Finds entries with **ALL** specified tags (AND logic)
|
|
- For OR logic, make separate requests
|
|
|
|
---
|
|
|
|
### Recent Entries
|
|
|
|
Get recently modified entries.
|
|
|
|
```http
|
|
GET /api/knowledge/search/recent
|
|
```
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Default | Description |
|
|
| --------- | ------- | ------- | --------------------------- |
|
|
| `limit` | integer | 10 | Number of entries (max: 50) |
|
|
| `status` | enum | - | DRAFT, PUBLISHED, ARCHIVED |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/search/recent?limit=5&status=PUBLISHED' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"id": "entry-uuid",
|
|
"slug": "react-hooks-guide",
|
|
"title": "React Hooks Guide",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "PUBLISHED",
|
|
"updatedAt": "2024-01-30T16:00:00Z",
|
|
"tags": [...]
|
|
}
|
|
],
|
|
"count": 5
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Sorted by `updatedAt` descending (newest first)
|
|
- Does not include full content (use Get Entry for details)
|
|
|
|
---
|
|
|
|
## Tag Endpoints
|
|
|
|
### List Tags
|
|
|
|
Get all tags in the workspace.
|
|
|
|
```http
|
|
GET /api/knowledge/tags
|
|
```
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/tags' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
[
|
|
{
|
|
"id": "tag-uuid-1",
|
|
"name": "React",
|
|
"slug": "react",
|
|
"color": "#61dafb",
|
|
"description": "React library and ecosystem"
|
|
},
|
|
{
|
|
"id": "tag-uuid-2",
|
|
"name": "Frontend",
|
|
"slug": "frontend",
|
|
"color": "#3b82f6",
|
|
"description": null
|
|
}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
### Get Tag
|
|
|
|
Get a single tag by slug.
|
|
|
|
```http
|
|
GET /api/knowledge/tags/:slug
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------ | ----------- |
|
|
| `slug` | string | Tag slug |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/tags/react' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"id": "tag-uuid",
|
|
"name": "React",
|
|
"slug": "react",
|
|
"color": "#61dafb",
|
|
"description": "React library and ecosystem"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Create Tag
|
|
|
|
Create a new tag.
|
|
|
|
```http
|
|
POST /api/knowledge/tags
|
|
```
|
|
|
|
**Permissions:** WORKSPACE_MEMBER
|
|
|
|
**Request Body:**
|
|
|
|
```json
|
|
{
|
|
"name": "TypeScript",
|
|
"color": "#3178c6",
|
|
"description": "TypeScript language and tooling"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Constraints |
|
|
| ------------- | ------ | -------- | --------------------------- |
|
|
| `name` | string | Yes | Unique per workspace |
|
|
| `color` | string | No | Hex color (e.g., "#3178c6") |
|
|
| `description` | string | No | - |
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X POST 'http://localhost:3001/api/knowledge/tags' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "TypeScript",
|
|
"color": "#3178c6"
|
|
}'
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"id": "new-tag-uuid",
|
|
"name": "TypeScript",
|
|
"slug": "typescript",
|
|
"color": "#3178c6",
|
|
"description": null
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Slug is auto-generated from name
|
|
- Tags are workspace-scoped (same slug can exist in different workspaces)
|
|
|
|
---
|
|
|
|
### Update Tag
|
|
|
|
Update an existing tag.
|
|
|
|
```http
|
|
PUT /api/knowledge/tags/:slug
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------ | ----------- |
|
|
| `slug` | string | Tag slug |
|
|
|
|
**Permissions:** WORKSPACE_MEMBER
|
|
|
|
**Request Body:**
|
|
|
|
All fields are optional. Only provided fields are updated.
|
|
|
|
```json
|
|
{
|
|
"name": "TypeScript (Updated)",
|
|
"color": "#2f74c0",
|
|
"description": "TypeScript programming language"
|
|
}
|
|
```
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X PUT 'http://localhost:3001/api/knowledge/tags/typescript' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"color": "#2f74c0"
|
|
}'
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"id": "tag-uuid",
|
|
"name": "TypeScript (Updated)",
|
|
"slug": "typescript",
|
|
"color": "#2f74c0",
|
|
"description": "TypeScript programming language"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Delete Tag
|
|
|
|
Delete a tag (removes from all entries).
|
|
|
|
```http
|
|
DELETE /api/knowledge/tags/:slug
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------ | ----------- |
|
|
| `slug` | string | Tag slug |
|
|
|
|
**Permissions:** WORKSPACE_ADMIN
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X DELETE 'http://localhost:3001/api/knowledge/tags/typescript' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```
|
|
204 No Content
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Tag is removed from all entries that used it
|
|
- Entries themselves are NOT deleted
|
|
|
|
---
|
|
|
|
### Get Tag Entries
|
|
|
|
Get all entries with a specific tag.
|
|
|
|
```http
|
|
GET /api/knowledge/tags/:slug/entries
|
|
```
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Description |
|
|
| --------- | ------ | ----------- |
|
|
| `slug` | string | Tag slug |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/tags/react/entries' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
[
|
|
{
|
|
"id": "entry-uuid-1",
|
|
"slug": "react-hooks-guide",
|
|
"title": "React Hooks Guide",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "PUBLISHED",
|
|
"createdAt": "2024-01-30T10:00:00Z",
|
|
"updatedAt": "2024-01-30T12:00:00Z"
|
|
},
|
|
{
|
|
"id": "entry-uuid-2",
|
|
"slug": "component-patterns",
|
|
"title": "React Component Patterns",
|
|
"summary": null,
|
|
"status": "PUBLISHED",
|
|
"createdAt": "2024-01-29T08:00:00Z",
|
|
"updatedAt": "2024-01-29T09:00:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## Import/Export Endpoints
|
|
|
|
### Export Entries
|
|
|
|
Export knowledge entries as a downloadable archive.
|
|
|
|
```http
|
|
GET /api/knowledge/export
|
|
```
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Default | Description |
|
|
| ---------- | -------- | -------- | ------------------------------------- |
|
|
| `format` | enum | markdown | Export format: `markdown` or `json` |
|
|
| `entryIds` | string[] | - | Optional array of entry IDs to export |
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Requests:**
|
|
|
|
Export all entries as Markdown:
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/export?format=markdown' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID" \
|
|
-o knowledge-export.zip
|
|
```
|
|
|
|
Export specific entries as JSON:
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/export?format=json&entryIds[]=uuid1&entryIds[]=uuid2' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID" \
|
|
-o knowledge-export.zip
|
|
```
|
|
|
|
**Response:**
|
|
|
|
Binary `.zip` file with headers:
|
|
|
|
```
|
|
Content-Type: application/zip
|
|
Content-Disposition: attachment; filename="knowledge-export-YYYYMMDD-HHMMSS.zip"
|
|
```
|
|
|
|
**Markdown format structure:**
|
|
|
|
```
|
|
knowledge-export.zip
|
|
├── react-hooks-guide.md
|
|
├── component-patterns.md
|
|
└── state-management.md
|
|
```
|
|
|
|
Each `.md` file:
|
|
|
|
```markdown
|
|
---
|
|
slug: react-hooks-guide
|
|
title: React Hooks Guide
|
|
status: PUBLISHED
|
|
visibility: WORKSPACE
|
|
tags: react, frontend
|
|
createdAt: 2024-01-30T10:00:00Z
|
|
updatedAt: 2024-01-30T12:00:00Z
|
|
---
|
|
|
|
# React Hooks Guide
|
|
|
|
Content with [[wiki-links]]...
|
|
```
|
|
|
|
**JSON format structure:**
|
|
|
|
```
|
|
knowledge-export.zip
|
|
└── entries.json
|
|
```
|
|
|
|
`entries.json`:
|
|
|
|
```json
|
|
[
|
|
{
|
|
"slug": "react-hooks-guide",
|
|
"title": "React Hooks Guide",
|
|
"content": "# React Hooks Guide\n\nContent...",
|
|
"summary": "Learn about React Hooks",
|
|
"status": "PUBLISHED",
|
|
"visibility": "WORKSPACE",
|
|
"tags": ["react", "frontend"],
|
|
"createdAt": "2024-01-30T10:00:00Z",
|
|
"updatedAt": "2024-01-30T12:00:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
### Import Entries
|
|
|
|
Import knowledge entries from uploaded file.
|
|
|
|
```http
|
|
POST /api/knowledge/import
|
|
```
|
|
|
|
**Permissions:** WORKSPACE_MEMBER
|
|
|
|
**Request:**
|
|
|
|
Multipart form data with file upload.
|
|
|
|
```bash
|
|
curl -X POST 'http://localhost:3001/api/knowledge/import' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID" \
|
|
-F "file=@knowledge-export.zip"
|
|
```
|
|
|
|
**Supported file types:**
|
|
|
|
- `.md` (single Markdown file)
|
|
- `.zip` (archive of Markdown files)
|
|
|
|
**File size limit:** 50MB
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"totalFiles": 10,
|
|
"imported": 8,
|
|
"failed": 2,
|
|
"results": [
|
|
{
|
|
"filename": "react-hooks.md",
|
|
"success": true,
|
|
"entryId": "new-entry-uuid",
|
|
"slug": "react-hooks"
|
|
},
|
|
{
|
|
"filename": "invalid-entry.md",
|
|
"success": false,
|
|
"error": "Title is required"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Import behavior:**
|
|
|
|
- New entries created with status DRAFT
|
|
- Existing entries (matching slug) are skipped
|
|
- Tags created automatically if they don't exist
|
|
- Wiki-links preserved (will resolve if targets exist)
|
|
|
|
**Front matter parsing:**
|
|
|
|
The importer reads YAML front matter:
|
|
|
|
```markdown
|
|
---
|
|
slug: custom-slug
|
|
title: Entry Title
|
|
summary: Optional summary
|
|
status: PUBLISHED
|
|
visibility: WORKSPACE
|
|
tags: tag1, tag2
|
|
---
|
|
|
|
Content starts here...
|
|
```
|
|
|
|
If no front matter, slug is generated from filename.
|
|
|
|
---
|
|
|
|
## Stats Endpoints
|
|
|
|
### Get Statistics
|
|
|
|
Get knowledge base statistics for the workspace.
|
|
|
|
```http
|
|
GET /api/knowledge/stats
|
|
```
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/stats' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"totalEntries": 42,
|
|
"entriesByStatus": {
|
|
"DRAFT": 5,
|
|
"PUBLISHED": 35,
|
|
"ARCHIVED": 2
|
|
},
|
|
"totalTags": 12,
|
|
"totalLinks": 87,
|
|
"totalVersions": 215,
|
|
"averageVersionsPerEntry": 5.1,
|
|
"topTags": [
|
|
{
|
|
"id": "tag-uuid",
|
|
"name": "React",
|
|
"slug": "react",
|
|
"count": 15
|
|
},
|
|
{
|
|
"id": "tag-uuid",
|
|
"name": "Frontend",
|
|
"slug": "frontend",
|
|
"count": 12
|
|
}
|
|
],
|
|
"mostLinkedEntries": [
|
|
{
|
|
"id": "entry-uuid",
|
|
"slug": "react-hooks",
|
|
"title": "React Hooks",
|
|
"incomingLinkCount": 8
|
|
}
|
|
],
|
|
"recentActivity": {
|
|
"last24h": 5,
|
|
"last7days": 18,
|
|
"last30days": 42
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Cache Endpoints
|
|
|
|
### Get Cache Stats
|
|
|
|
Get cache performance statistics.
|
|
|
|
```http
|
|
GET /api/knowledge/cache/stats
|
|
```
|
|
|
|
**Permissions:** WORKSPACE_ANY
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X GET 'http://localhost:3001/api/knowledge/cache/stats' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"enabled": true,
|
|
"stats": {
|
|
"hits": 1250,
|
|
"misses": 180,
|
|
"sets": 195,
|
|
"deletes": 15,
|
|
"hitRate": 0.874
|
|
}
|
|
}
|
|
```
|
|
|
|
**Stats explanation:**
|
|
|
|
- `hits`: Cache hits (data found in cache)
|
|
- `misses`: Cache misses (data not in cache, fetched from DB)
|
|
- `sets`: Cache writes
|
|
- `deletes`: Cache invalidations
|
|
- `hitRate`: Percentage of requests served from cache (hits / (hits + misses))
|
|
|
|
---
|
|
|
|
### Clear Cache
|
|
|
|
Clear all cached data for the workspace.
|
|
|
|
```http
|
|
POST /api/knowledge/cache/clear
|
|
```
|
|
|
|
**Permissions:** WORKSPACE_ADMIN
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X POST 'http://localhost:3001/api/knowledge/cache/clear' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"message": "Cache cleared successfully"
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Clears ALL knowledge caches for the workspace
|
|
- Includes entry caches, search caches, and graph caches
|
|
- Does not affect other workspaces
|
|
|
|
---
|
|
|
|
### Reset Cache Stats
|
|
|
|
Reset cache statistics counters to zero.
|
|
|
|
```http
|
|
POST /api/knowledge/cache/stats/reset
|
|
```
|
|
|
|
**Permissions:** WORKSPACE_ADMIN
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
curl -X POST 'http://localhost:3001/api/knowledge/cache/stats/reset' \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "x-workspace-id: WORKSPACE_ID"
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"message": "Cache statistics reset successfully"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Responses
|
|
|
|
All endpoints follow standard HTTP error codes and return JSON error objects.
|
|
|
|
### Error Format
|
|
|
|
```json
|
|
{
|
|
"statusCode": 404,
|
|
"message": "Entry not found",
|
|
"error": "Not Found"
|
|
}
|
|
```
|
|
|
|
### Common Status Codes
|
|
|
|
| Code | Meaning | Example |
|
|
| ----- | --------------------- | -------------------------------------------- |
|
|
| `400` | Bad Request | Invalid request body, missing required field |
|
|
| `401` | Unauthorized | Missing or invalid auth token |
|
|
| `403` | Forbidden | Insufficient permissions for action |
|
|
| `404` | Not Found | Entry, tag, or version doesn't exist |
|
|
| `409` | Conflict | Slug already exists, duplicate entry |
|
|
| `413` | Payload Too Large | Import file exceeds 50MB limit |
|
|
| `422` | Unprocessable Entity | Validation failed (e.g., invalid enum value) |
|
|
| `500` | Internal Server Error | Unexpected server error |
|
|
|
|
### Validation Errors
|
|
|
|
When validation fails, you get detailed error information:
|
|
|
|
```json
|
|
{
|
|
"statusCode": 422,
|
|
"message": [
|
|
"title must not be empty",
|
|
"title must not exceed 500 characters",
|
|
"status must be a valid EntryStatus"
|
|
],
|
|
"error": "Unprocessable Entity"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## JavaScript Examples
|
|
|
|
### Using Fetch API
|
|
|
|
```javascript
|
|
const API_URL = "http://localhost:3001/api";
|
|
const token = "YOUR_SESSION_TOKEN";
|
|
const workspaceId = "YOUR_WORKSPACE_ID";
|
|
|
|
const headers = {
|
|
Authorization: `Bearer ${token}`,
|
|
"x-workspace-id": workspaceId,
|
|
"Content-Type": "application/json",
|
|
};
|
|
|
|
// Create entry
|
|
async function createEntry() {
|
|
const response = await fetch(`${API_URL}/knowledge/entries`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({
|
|
title: "My New Entry",
|
|
content: "# Hello World\n\nThis is my first [[wiki-link]]!",
|
|
tags: ["tutorial"],
|
|
status: "DRAFT",
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed: ${response.status}`);
|
|
}
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
// Search entries
|
|
async function searchEntries(query) {
|
|
const params = new URLSearchParams({ q: query, limit: 10 });
|
|
const response = await fetch(`${API_URL}/knowledge/search?${params}`, {
|
|
headers,
|
|
});
|
|
|
|
return await response.json();
|
|
}
|
|
|
|
// Export entries
|
|
async function exportEntries() {
|
|
const response = await fetch(`${API_URL}/knowledge/export?format=markdown`, {
|
|
headers,
|
|
});
|
|
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = "knowledge-export.zip";
|
|
a.click();
|
|
}
|
|
```
|
|
|
|
### Using Axios
|
|
|
|
```javascript
|
|
import axios from "axios";
|
|
|
|
const api = axios.create({
|
|
baseURL: "http://localhost:3001/api",
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
"x-workspace-id": workspaceId,
|
|
},
|
|
});
|
|
|
|
// Get entry
|
|
const entry = await api.get("/knowledge/entries/react-hooks");
|
|
console.log(entry.data);
|
|
|
|
// Update entry
|
|
const updated = await api.put("/knowledge/entries/react-hooks", {
|
|
status: "PUBLISHED",
|
|
changeNote: "Ready for publication",
|
|
});
|
|
|
|
// Import file
|
|
const formData = new FormData();
|
|
formData.append("file", fileInput.files[0]);
|
|
const result = await api.post("/knowledge/import", formData, {
|
|
headers: { "Content-Type": "multipart/form-data" },
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
- **[User Guide](KNOWLEDGE_USER_GUIDE.md)** — Learn how to use the Knowledge Module
|
|
- **[Developer Guide](KNOWLEDGE_DEV.md)** — Architecture and implementation details
|
|
- **[Main README](README.md)** — Complete Mosaic Stack documentation
|
|
|
|
---
|
|
|
|
**Happy building! 🚀**
|