> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Ishaq74/concordia/llms.txt
> Use this file to discover all available pages before exploring further.

# Blog API

> Manage blog articles, authors, categories, comments, and media

## Overview

The Blog API provides comprehensive content management endpoints for articles, authors, categories, comments, and media.

All Blog API endpoints require **admin authentication**.

***

## Articles

### List Articles

<CodeGroup>
  ```bash GET Request theme={null}
  curl "https://your-domain.com/api/admin/blog/articles?page=1&perPage=20&status=published" \
    -H "Authorization: Bearer <token>"
  ```

  ```json Response theme={null}
  {
    "articles": [
      {
        "id": "art_123",
        "slug": "welcome-to-concordia",
        "status": "published",
        "publishedAt": "2024-03-01T10:00:00Z",
        "isFeatured": true,
        "displayInHome": true,
        "displayInBlog": true,
        "translations": [
          {
            "inLanguage": "fr",
            "headline": "Bienvenue à Concordia"
          }
        ],
        "authors": ["John Doe"],
        "categoryIds": ["cat_001"]
      }
    ],
    "total": 150,
    "page": 1,
    "perPage": 20,
    "totalPages": 8
  }
  ```
</CodeGroup>

#### Query Parameters

<ParamField query="page" type="integer" default="1">
  Page number (1-indexed)
</ParamField>

<ParamField query="perPage" type="integer" default="20">
  Items per page (max: 100)
</ParamField>

<ParamField query="q" type="string">
  Search by slug
</ParamField>

<ParamField query="status" type="string">
  Filter by status: `draft`, `published`, `scheduled`, `archived`
</ParamField>

<ParamField query="featured" type="boolean">
  Filter featured articles (`true` or `false`)
</ParamField>

<ParamField query="home" type="boolean">
  Filter articles displayed on homepage
</ParamField>

<ParamField query="blog" type="boolean">
  Filter articles displayed in blog listing
</ParamField>

<ParamField query="category" type="string">
  Filter by category ID
</ParamField>

<ParamField query="organizationId" type="string">
  Filter by organization ID
</ParamField>

<ParamField query="id" type="string">
  Fetch single article by ID (returns full details with translations, authors, categories, media)
</ParamField>

***

### Create Article

From `/src/pages/api/admin/blog/articles.ts:197-297`:

<CodeGroup>
  ```bash POST Request theme={null}
  curl -X POST "https://your-domain.com/api/admin/blog/articles" \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d @article.json
  ```

  ```json article.json theme={null}
  {
    "action": "create",
    "slug": "welcome-post",
    "status": "draft",
    "inLanguage": "fr",
    "organizationId": "org_123",
    "displayInHome": true,
    "displayInBlog": true,
    "isFeatured": false,
    "allowComments": true,
    "readingTime": "5 min",
    "translations": [
      {
        "inLanguage": "fr",
        "headline": "Article de bienvenue",
        "articleBody": "Contenu de l'article...",
        "excerpt": "Courte description",
        "seoTitle": "Article SEO Title",
        "seoDescription": "Article SEO description"
      }
    ],
    "authorIds": ["author_001"],
    "categoryIds": ["cat_001", "cat_002"],
    "media": [
      {
        "mediaId": "media_123",
        "type": "cover",
        "position": "hero"
      }
    ]
  }
  ```

  ```json Response theme={null}
  {
    "ok": true,
    "id": "art_456",
    "slug": "welcome-post"
  }
  ```
</CodeGroup>

#### Request Body

<ParamField body="action" type="string" required>
  Must be `"create"`
</ParamField>

<ParamField body="slug" type="string" required>
  URL-friendly slug (auto-generated if not provided)
</ParamField>

<ParamField body="status" type="string" default="draft">
  Article status: `draft`, `published`, `scheduled`, `archived`
</ParamField>

<ParamField body="inLanguage" type="string" default="fr">
  Primary language code (ISO 639-1)
</ParamField>

<ParamField body="organizationId" type="string">
  Organization ID (nullable)
</ParamField>

<ParamField body="displayInHome" type="boolean" default="false">
  Show on homepage
</ParamField>

<ParamField body="displayInBlog" type="boolean" default="true">
  Show in blog listing
</ParamField>

<ParamField body="isFeatured" type="boolean" default="false">
  Mark as featured
</ParamField>

<ParamField body="allowComments" type="boolean" default="true">
  Enable comments
</ParamField>

<ParamField body="readingTime" type="string">
  Estimated reading time (e.g., "5 min")
</ParamField>

<ParamField body="wordCount" type="string">
  Article word count
</ParamField>

<ParamField body="translations" type="array">
  Array of translation objects (see structure above)
</ParamField>

<ParamField body="authorIds" type="string[]">
  Array of author IDs
</ParamField>

<ParamField body="categoryIds" type="string[]">
  Array of category IDs
</ParamField>

<ParamField body="media" type="array">
  Array of media objects with `mediaId`, `type`, `position`
</ParamField>

***

### Update Article

From `/src/pages/api/admin/blog/articles.ts:299-409`:

```json theme={null}
{
  "action": "update",
  "id": "art_456",
  "slug": "updated-slug",
  "status": "published",
  "isFeatured": true,
  "translations": [...],
  "authorIds": [...],
  "categoryIds": [...]
}
```

Updating an article **replaces** translations, authors, categories, and media (full replacement strategy).

***

### Other Article Actions

From `/src/pages/api/admin/blog/articles.ts:411-534`:

#### Publish Article

```json theme={null}
{
  "action": "publish",
  "id": "art_456"
}
```

Sets `status` to `"published"` and sets `publishedAt` to current timestamp.

#### Unpublish Article

```json theme={null}
{
  "action": "unpublish",
  "id": "art_456"
}
```

#### Delete Article

```json theme={null}
{
  "action": "delete",
  "id": "art_456"
}
```

Deletes the article and all related data (translations, authors, categories, media).

#### Duplicate Article

```json theme={null}
{
  "action": "duplicate",
  "id": "art_456"
}
```

Creates a copy with status `"draft"` and unique slug.

***

## Authors

### List Authors

From `/src/pages/api/admin/blog/authors.ts:19-98`:

```bash theme={null}
GET /api/admin/blog/authors?page=1&perPage=20
```

#### Query Parameters

<ParamField query="id" type="string">
  Fetch single author by ID (includes article count)
</ParamField>

<ParamField query="all" type="boolean">
  Return all authors (for selectors) with minimal fields
</ParamField>

<ParamField query="q" type="string">
  Search by slug
</ParamField>

<ParamField query="featured" type="boolean">
  Filter featured authors
</ParamField>

<ParamField query="home" type="boolean">
  Filter authors displayed on homepage
</ParamField>

<ParamField query="blog" type="boolean">
  Filter authors displayed in blog
</ParamField>

***

### Create Author

From `/src/pages/api/admin/blog/authors.ts:122-166`:

```json theme={null}
{
  "action": "create",
  "slug": "john-doe",
  "displayName": { "fr": "John Doe", "en": "John Doe" },
  "givenName": { "fr": "John" },
  "familyName": { "fr": "Doe" },
  "bio": { "fr": "Auteur passionné" },
  "jobTitle": { "fr": "Journaliste" },
  "email": "john@example.com",
  "avatarUrl": "https://example.com/avatar.jpg",
  "website": "https://johndoe.com",
  "displayInHome": false,
  "displayInBlog": true,
  "isFeatured": false
}
```

#### Response

```json theme={null}
{
  "ok": true,
  "id": "author_789",
  "slug": "john-doe"
}
```

***

### Update/Delete Author

From `/src/pages/api/admin/blog/authors.ts:168-238`:

```json theme={null}
// Update
{ "action": "update", "id": "author_789", "displayName": {...} }

// Delete
{ "action": "delete", "id": "author_789" }
```

***

## Categories

### List Categories

From `/src/pages/api/admin/blog/categories.ts:18-102`:

```bash theme={null}
GET /api/admin/blog/categories?all=true
```

#### Query Parameters

<ParamField query="id" type="string">
  Fetch single category by ID
</ParamField>

<ParamField query="all" type="boolean">
  Return all categories (for selectors)
</ParamField>

<ParamField query="parent" type="string">
  Filter by parent ID (use `"root"` for top-level)
</ParamField>

<ParamField query="menu" type="boolean">
  Filter categories shown in menu
</ParamField>

***

### Create Category

From `/src/pages/api/admin/blog/categories.ts:126-165`:

```json theme={null}
{
  "action": "create",
  "slug": "tech",
  "name": { "fr": "Technologie", "en": "Technology" },
  "description": { "fr": "Articles sur la technologie" },
  "parentId": null,
  "displayInHome": true,
  "displayInMenu": true,
  "displayInBlog": true,
  "isFeatured": false
}
```

***

## Comments

### List Comments

From `/src/pages/api/admin/blog/comments.ts:13-66`:

```bash theme={null}
GET /api/admin/blog/comments?status=pending&page=1
```

#### Query Parameters

<ParamField query="status" type="string">
  Filter by status: `pending`, `approved`, `rejected`
</ParamField>

<ParamField query="type" type="string">
  Filter by post type
</ParamField>

<ParamField query="q" type="string">
  Search by author name or email
</ParamField>

#### Response

```json theme={null}
{
  "comments": [...],
  "total": 42,
  "page": 1,
  "perPage": 25,
  "totalPages": 2,
  "stats": {
    "pending": 12,
    "approved": 28,
    "rejected": 2
  }
}
```

***

### Moderate Comments

From `/src/pages/api/admin/blog/comments.ts:72-162`:

<CodeGroup>
  ```json Approve theme={null}
  {
    "action": "approve",
    "id": "comment_123"
  }
  ```

  ```json Reject theme={null}
  {
    "action": "reject",
    "id": "comment_123"
  }
  ```

  ```json Delete theme={null}
  {
    "action": "delete",
    "id": "comment_123"
  }
  ```
</CodeGroup>

***

## Media

### List Media

From `/src/pages/api/admin/blog/media.ts:20-51`:

```bash theme={null}
GET /api/admin/blog/media?type=image&page=1&perPage=30
```

#### Query Parameters

<ParamField query="type" type="string">
  Filter by media type: `image`, `video`, `audio`
</ParamField>

<ParamField query="q" type="string">
  Search by URL
</ParamField>

***

### Upload Media

From `/src/pages/api/admin/blog/media.ts:57-210`:

<CodeGroup>
  ```bash Upload theme={null}
  curl -X POST "https://your-domain.com/api/admin/blog/media" \
    -H "Authorization: Bearer <token>" \
    -F "file=@image.jpg" \
    -F "alt=Image description" \
    -F "caption=Photo caption" \
    -F "customName=my-image"
  ```

  ```json Response theme={null}
  {
    "ok": true,
    "id": "media_999",
    "url": "/uploads/blog/my-image.jpg"
  }
  ```
</CodeGroup>

#### Configuration

* **Upload directory**: `public/uploads/blog`
* **Max file size**: 10 MB
* **Allowed types**: JPEG, PNG, WebP, AVIF, GIF, SVG

#### Form Fields

<ParamField body="file" type="File" required>
  Image file to upload
</ParamField>

<ParamField body="customName" type="string">
  Custom filename (auto-sanitized)
</ParamField>

<ParamField body="alt" type="string">
  Alt text for accessibility
</ParamField>

<ParamField body="caption" type="string">
  Image caption
</ParamField>

<ParamField body="description" type="string">
  Detailed description
</ParamField>

***

### Update/Delete Media

From `/src/pages/api/admin/blog/media.ts:66-115`:

<CodeGroup>
  ```json Update Metadata theme={null}
  {
    "action": "update",
    "id": "media_999",
    "caption": { "fr": "Nouvelle légende" },
    "alt": { "fr": "Nouveau texte alternatif" }
  }
  ```

  ```json Delete theme={null}
  {
    "action": "delete",
    "id": "media_999"
  }
  ```
</CodeGroup>

Deleting media also removes the file from disk.

***

## Common Response Codes

| Code | Description                                      |
| ---- | ------------------------------------------------ |
| 200  | Success                                          |
| 201  | Created                                          |
| 400  | Invalid request (missing fields, invalid action) |
| 403  | Not admin (forbidden)                            |
| 404  | Resource not found                               |
| 500  | Server error                                     |

***

## Audit Logging

All Blog API actions are logged to `audit_log` table:

* `blog.article.create`
* `blog.article.update`
* `blog.article.delete`
* `blog.article.publish`
* `blog.article.unpublish`
* `blog.article.duplicate`
* `blog.author.create`
* `blog.author.update`
* `blog.author.delete`
* `blog.category.create`
* `blog.category.update`
* `blog.category.delete`
* `blog.comment.approve`
* `blog.comment.reject`
* `blog.comment.delete`
* `blog.media.upload`
* `blog.media.delete`

***

## Related Resources

<CardGroup cols={2}>
  <Card title="API Overview" icon="globe" href="/development/api/overview">
    Authentication and rate limiting
  </Card>

  <Card title="Services API" icon="briefcase" href="/development/api/services">
    Manage service listings
  </Card>
</CardGroup>
