@nextsparkjs/theme-blog 0.1.0-beta.44 → 0.1.0-beta.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/authors/docs.md +135 -0
- package/api/authors/presets.ts +45 -0
- package/api/posts/public/docs.md +124 -0
- package/api/posts/public/presets.ts +65 -0
- package/entities/categories/api/docs.md +119 -0
- package/entities/categories/api/presets.ts +67 -0
- package/entities/posts/api/docs.md +174 -0
- package/entities/posts/api/presets.ts +137 -0
- package/lib/selectors.ts +2 -2
- package/package.json +3 -3
- package/styles/globals.css +27 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Authors API
|
|
2
|
+
|
|
3
|
+
Public author profiles and post listings. No authentication required.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Authors API provides read-only access to author profiles and their published posts. This endpoint is designed for public consumption and does not require authentication.
|
|
8
|
+
|
|
9
|
+
## Authentication
|
|
10
|
+
|
|
11
|
+
**No authentication required** - This is a public endpoint.
|
|
12
|
+
|
|
13
|
+
## Endpoints
|
|
14
|
+
|
|
15
|
+
### List Authors
|
|
16
|
+
`GET /api/v1/theme/blog/authors`
|
|
17
|
+
|
|
18
|
+
Returns a list of all authors who have published posts.
|
|
19
|
+
|
|
20
|
+
**Example Request:**
|
|
21
|
+
```bash
|
|
22
|
+
curl https://example.com/api/v1/theme/blog/authors
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Example Response:**
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"data": [
|
|
29
|
+
{
|
|
30
|
+
"id": "user_abc123",
|
|
31
|
+
"name": "John Doe",
|
|
32
|
+
"username": "johndoe",
|
|
33
|
+
"bio": "Full-stack developer and tech writer",
|
|
34
|
+
"image": "https://example.com/avatar.jpg",
|
|
35
|
+
"postCount": 15
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "user_def456",
|
|
39
|
+
"name": "Jane Smith",
|
|
40
|
+
"username": "janesmith",
|
|
41
|
+
"bio": "Product designer and UX enthusiast",
|
|
42
|
+
"image": "https://example.com/avatar2.jpg",
|
|
43
|
+
"postCount": 8
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Get Author Profile
|
|
50
|
+
`GET /api/v1/theme/blog/authors/[username]`
|
|
51
|
+
|
|
52
|
+
Returns an author's profile with their published posts.
|
|
53
|
+
|
|
54
|
+
**Path Parameters:**
|
|
55
|
+
- `username` (string, required): Author's username
|
|
56
|
+
|
|
57
|
+
**Example Request:**
|
|
58
|
+
```bash
|
|
59
|
+
curl https://example.com/api/v1/theme/blog/authors/johndoe
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Example Response:**
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"author": {
|
|
66
|
+
"id": "user_abc123",
|
|
67
|
+
"name": "John Doe",
|
|
68
|
+
"username": "johndoe",
|
|
69
|
+
"bio": "Full-stack developer and tech writer",
|
|
70
|
+
"image": "https://example.com/avatar.jpg"
|
|
71
|
+
},
|
|
72
|
+
"posts": [
|
|
73
|
+
{
|
|
74
|
+
"id": "post_123",
|
|
75
|
+
"title": "Getting Started with Next.js",
|
|
76
|
+
"slug": "getting-started-with-nextjs",
|
|
77
|
+
"excerpt": "A comprehensive guide...",
|
|
78
|
+
"featuredImage": "https://example.com/image.jpg",
|
|
79
|
+
"publishedAt": "2024-01-15T10:30:00Z",
|
|
80
|
+
"categories": [
|
|
81
|
+
{
|
|
82
|
+
"id": "cat_123",
|
|
83
|
+
"name": "Technology",
|
|
84
|
+
"slug": "technology"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"stats": {
|
|
90
|
+
"totalPosts": 15
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Response Fields
|
|
96
|
+
|
|
97
|
+
### Author Object
|
|
98
|
+
|
|
99
|
+
| Field | Type | Description |
|
|
100
|
+
|-------|------|-------------|
|
|
101
|
+
| id | string | Author user ID |
|
|
102
|
+
| name | string | Author display name |
|
|
103
|
+
| username | string | Author username (URL-friendly) |
|
|
104
|
+
| bio | string | Author biography |
|
|
105
|
+
| image | string | Author avatar URL |
|
|
106
|
+
| postCount | number | Number of published posts (list endpoint only) |
|
|
107
|
+
|
|
108
|
+
### Author Profile Response
|
|
109
|
+
|
|
110
|
+
| Field | Type | Description |
|
|
111
|
+
|-------|------|-------------|
|
|
112
|
+
| author | object | Author profile information |
|
|
113
|
+
| posts | array | Array of published posts |
|
|
114
|
+
| stats.totalPosts | number | Total number of published posts |
|
|
115
|
+
|
|
116
|
+
## Features
|
|
117
|
+
|
|
118
|
+
- **No Auth Required**: Accessible without authentication
|
|
119
|
+
- **Rate Limited**: 100 requests per minute per IP
|
|
120
|
+
- **Cached**: Responses cached for 60 seconds
|
|
121
|
+
- **Sorted by Post Count**: Authors with more posts appear first
|
|
122
|
+
|
|
123
|
+
## Error Responses
|
|
124
|
+
|
|
125
|
+
| Status | Description |
|
|
126
|
+
|--------|-------------|
|
|
127
|
+
| 400 | Bad Request - Invalid parameters |
|
|
128
|
+
| 404 | Not Found - Author not found |
|
|
129
|
+
| 429 | Too Many Requests - Rate limit exceeded |
|
|
130
|
+
| 500 | Internal Server Error |
|
|
131
|
+
|
|
132
|
+
## Related APIs
|
|
133
|
+
|
|
134
|
+
- **[Public Posts](/api/v1/theme/blog/posts/public)** - Public post feed
|
|
135
|
+
- **[Posts](/api/v1/posts)** - Authenticated CRUD operations
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Presets for Authors Route
|
|
3
|
+
*
|
|
4
|
+
* Public author profiles and their published posts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineApiEndpoint } from '@nextsparkjs/core/types/api-presets'
|
|
8
|
+
|
|
9
|
+
export default defineApiEndpoint({
|
|
10
|
+
endpoint: '/api/v1/theme/blog/authors',
|
|
11
|
+
summary: 'Public author profiles with published posts',
|
|
12
|
+
presets: [
|
|
13
|
+
{
|
|
14
|
+
id: 'list-all',
|
|
15
|
+
title: 'List All Authors',
|
|
16
|
+
description: 'Get all authors with published posts',
|
|
17
|
+
method: 'GET',
|
|
18
|
+
params: {
|
|
19
|
+
limit: 50
|
|
20
|
+
},
|
|
21
|
+
tags: ['read', 'list', 'public']
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'get-by-username',
|
|
25
|
+
title: 'Get Author by Username',
|
|
26
|
+
description: 'Get author profile with their posts',
|
|
27
|
+
method: 'GET',
|
|
28
|
+
pathParams: {
|
|
29
|
+
username: '{{username}}'
|
|
30
|
+
},
|
|
31
|
+
tags: ['read', 'detail', 'public']
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'list-with-post-count',
|
|
35
|
+
title: 'List with Post Count',
|
|
36
|
+
description: 'Get authors sorted by number of posts',
|
|
37
|
+
method: 'GET',
|
|
38
|
+
params: {
|
|
39
|
+
sortBy: 'postCount',
|
|
40
|
+
sortOrder: 'desc'
|
|
41
|
+
},
|
|
42
|
+
tags: ['read', 'list', 'public']
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
})
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Public Posts API
|
|
2
|
+
|
|
3
|
+
Public feed of published blog posts. No authentication required.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Public Posts API provides read-only access to published blog posts. This endpoint is designed for public consumption by blog visitors and does not require authentication.
|
|
8
|
+
|
|
9
|
+
## Authentication
|
|
10
|
+
|
|
11
|
+
**No authentication required** - This is a public endpoint.
|
|
12
|
+
|
|
13
|
+
## Endpoints
|
|
14
|
+
|
|
15
|
+
### List Published Posts
|
|
16
|
+
`GET /api/v1/theme/blog/posts/public`
|
|
17
|
+
|
|
18
|
+
Returns a paginated list of published posts.
|
|
19
|
+
|
|
20
|
+
**Query Parameters:**
|
|
21
|
+
- `limit` (number, optional): Maximum records to return. Default: 20, Max: 100
|
|
22
|
+
- `offset` (number, optional): Number of records to skip. Default: 0
|
|
23
|
+
- `category` (string, optional): Filter by category slug
|
|
24
|
+
|
|
25
|
+
**Example Request:**
|
|
26
|
+
```bash
|
|
27
|
+
curl https://example.com/api/v1/theme/blog/posts/public?limit=10&category=technology
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Example Response:**
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"data": [
|
|
34
|
+
{
|
|
35
|
+
"id": "post_abc123",
|
|
36
|
+
"title": "Getting Started with Next.js",
|
|
37
|
+
"slug": "getting-started-with-nextjs",
|
|
38
|
+
"excerpt": "A comprehensive guide to building modern web apps",
|
|
39
|
+
"featuredImage": "https://example.com/image.jpg",
|
|
40
|
+
"featured": true,
|
|
41
|
+
"publishedAt": "2024-01-15T10:30:00Z",
|
|
42
|
+
"author": {
|
|
43
|
+
"id": "user_123",
|
|
44
|
+
"name": "John Doe",
|
|
45
|
+
"username": "johndoe",
|
|
46
|
+
"image": "https://example.com/avatar.jpg"
|
|
47
|
+
},
|
|
48
|
+
"categories": [
|
|
49
|
+
{
|
|
50
|
+
"id": "cat_123",
|
|
51
|
+
"name": "Technology",
|
|
52
|
+
"slug": "technology"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"pagination": {
|
|
58
|
+
"total": 25,
|
|
59
|
+
"limit": 10,
|
|
60
|
+
"offset": 0,
|
|
61
|
+
"hasMore": true
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Response Fields
|
|
67
|
+
|
|
68
|
+
| Field | Type | Description |
|
|
69
|
+
|-------|------|-------------|
|
|
70
|
+
| id | string | Post unique identifier |
|
|
71
|
+
| title | string | Post title |
|
|
72
|
+
| slug | string | URL-friendly slug |
|
|
73
|
+
| excerpt | string | Brief description |
|
|
74
|
+
| featuredImage | string | Featured image URL |
|
|
75
|
+
| featured | boolean | Whether post is featured |
|
|
76
|
+
| publishedAt | datetime | Publication timestamp |
|
|
77
|
+
| author | object | Author information |
|
|
78
|
+
| author.id | string | Author user ID |
|
|
79
|
+
| author.name | string | Author display name |
|
|
80
|
+
| author.username | string | Author username |
|
|
81
|
+
| author.image | string | Author avatar URL |
|
|
82
|
+
| categories | array | Associated categories |
|
|
83
|
+
|
|
84
|
+
## Features
|
|
85
|
+
|
|
86
|
+
- **No Auth Required**: Accessible without authentication
|
|
87
|
+
- **Rate Limited**: 100 requests per minute per IP
|
|
88
|
+
- **Cached**: Responses cached for 60 seconds
|
|
89
|
+
- **Category Filter**: Filter posts by category slug
|
|
90
|
+
|
|
91
|
+
## Pagination
|
|
92
|
+
|
|
93
|
+
The API uses offset-based pagination:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"pagination": {
|
|
98
|
+
"total": 100,
|
|
99
|
+
"limit": 20,
|
|
100
|
+
"offset": 0,
|
|
101
|
+
"hasMore": true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
To get the next page:
|
|
107
|
+
```
|
|
108
|
+
GET /api/v1/theme/blog/posts/public?offset=20&limit=20
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Error Responses
|
|
112
|
+
|
|
113
|
+
| Status | Description |
|
|
114
|
+
|--------|-------------|
|
|
115
|
+
| 400 | Bad Request - Invalid query parameters |
|
|
116
|
+
| 404 | Not Found - Category not found |
|
|
117
|
+
| 429 | Too Many Requests - Rate limit exceeded |
|
|
118
|
+
| 500 | Internal Server Error |
|
|
119
|
+
|
|
120
|
+
## Related APIs
|
|
121
|
+
|
|
122
|
+
- **[Posts](/api/v1/posts)** - Authenticated CRUD operations
|
|
123
|
+
- **[Authors](/api/v1/theme/blog/authors)** - Author profiles
|
|
124
|
+
- **[Categories](/api/v1/categories)** - Category management
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Presets for Public Posts
|
|
3
|
+
*
|
|
4
|
+
* Public feed of published blog posts (no authentication required)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineApiEndpoint } from '@nextsparkjs/core/types/api-presets'
|
|
8
|
+
|
|
9
|
+
export default defineApiEndpoint({
|
|
10
|
+
endpoint: '/api/v1/theme/blog/posts/public',
|
|
11
|
+
summary: 'Public feed of published blog posts',
|
|
12
|
+
presets: [
|
|
13
|
+
{
|
|
14
|
+
id: 'list-recent',
|
|
15
|
+
title: 'List Recent Posts',
|
|
16
|
+
description: 'Get recent published posts',
|
|
17
|
+
method: 'GET',
|
|
18
|
+
params: {
|
|
19
|
+
limit: 20
|
|
20
|
+
},
|
|
21
|
+
tags: ['read', 'list', 'public']
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'list-with-limit',
|
|
25
|
+
title: 'List with Custom Limit',
|
|
26
|
+
description: 'Get posts with specific limit',
|
|
27
|
+
method: 'GET',
|
|
28
|
+
params: {
|
|
29
|
+
limit: '{{limit}}'
|
|
30
|
+
},
|
|
31
|
+
tags: ['read', 'list', 'public']
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'list-by-category',
|
|
35
|
+
title: 'List by Category',
|
|
36
|
+
description: 'Get posts filtered by category slug',
|
|
37
|
+
method: 'GET',
|
|
38
|
+
params: {
|
|
39
|
+
category: '{{category}}'
|
|
40
|
+
},
|
|
41
|
+
tags: ['read', 'filter', 'public']
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'list-paginated',
|
|
45
|
+
title: 'List Paginated',
|
|
46
|
+
description: 'Get paginated posts',
|
|
47
|
+
method: 'GET',
|
|
48
|
+
params: {
|
|
49
|
+
limit: '{{limit}}',
|
|
50
|
+
offset: '{{offset}}'
|
|
51
|
+
},
|
|
52
|
+
tags: ['read', 'list', 'public']
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'list-featured',
|
|
56
|
+
title: 'List Featured Posts',
|
|
57
|
+
description: 'Get featured published posts',
|
|
58
|
+
method: 'GET',
|
|
59
|
+
params: {
|
|
60
|
+
featured: 'true'
|
|
61
|
+
},
|
|
62
|
+
tags: ['read', 'filter', 'public']
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
})
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Categories API
|
|
2
|
+
|
|
3
|
+
Manage blog categories for organizing and filtering posts.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Categories API allows you to create, read, update, and delete category records. Categories provide a hierarchical organization system for blog posts through a many-to-many relationship.
|
|
8
|
+
|
|
9
|
+
## Authentication
|
|
10
|
+
|
|
11
|
+
All endpoints require authentication via:
|
|
12
|
+
- **Session cookie** (for browser-based requests)
|
|
13
|
+
- **API Key** header (for server-to-server requests)
|
|
14
|
+
|
|
15
|
+
## Endpoints
|
|
16
|
+
|
|
17
|
+
### List Categories
|
|
18
|
+
`GET /api/v1/categories`
|
|
19
|
+
|
|
20
|
+
Returns a paginated list of categories.
|
|
21
|
+
|
|
22
|
+
**Query Parameters:**
|
|
23
|
+
- `limit` (number, optional): Maximum records to return. Default: 20
|
|
24
|
+
- `offset` (number, optional): Number of records to skip. Default: 0
|
|
25
|
+
- `search` (string, optional): Search by name, description
|
|
26
|
+
- `sortBy` (string, optional): Field to sort by
|
|
27
|
+
- `sortOrder` (string, optional): Sort direction (asc, desc)
|
|
28
|
+
|
|
29
|
+
**Example Response:**
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"data": [
|
|
33
|
+
{
|
|
34
|
+
"id": "category_abc123",
|
|
35
|
+
"name": "Technology",
|
|
36
|
+
"slug": "technology",
|
|
37
|
+
"description": "Posts about tech and software development",
|
|
38
|
+
"createdAt": "2024-01-15T10:30:00Z",
|
|
39
|
+
"updatedAt": "2024-01-15T10:30:00Z"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"pagination": {
|
|
43
|
+
"total": 8,
|
|
44
|
+
"limit": 20,
|
|
45
|
+
"offset": 0
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Get Single Category
|
|
51
|
+
`GET /api/v1/categories/[id]`
|
|
52
|
+
|
|
53
|
+
Returns a single category by ID.
|
|
54
|
+
|
|
55
|
+
### Create Category
|
|
56
|
+
`POST /api/v1/categories`
|
|
57
|
+
|
|
58
|
+
Create a new category.
|
|
59
|
+
|
|
60
|
+
**Request Body:**
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"name": "Technology",
|
|
64
|
+
"slug": "technology",
|
|
65
|
+
"description": "Posts about tech and software development"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Update Category
|
|
70
|
+
`PATCH /api/v1/categories/[id]`
|
|
71
|
+
|
|
72
|
+
Update an existing category. Supports partial updates.
|
|
73
|
+
|
|
74
|
+
### Delete Category
|
|
75
|
+
`DELETE /api/v1/categories/[id]`
|
|
76
|
+
|
|
77
|
+
Delete a category record. This will remove the category from all associated posts.
|
|
78
|
+
|
|
79
|
+
## Fields
|
|
80
|
+
|
|
81
|
+
| Field | Type | Required | Description |
|
|
82
|
+
|-------|------|----------|-------------|
|
|
83
|
+
| name | text | Yes | Category name |
|
|
84
|
+
| slug | text | Yes | URL-friendly slug (auto-generated if not provided) |
|
|
85
|
+
| description | textarea | No | Category description |
|
|
86
|
+
| createdAt | datetime | Auto | Creation timestamp |
|
|
87
|
+
| updatedAt | datetime | Auto | Last update timestamp |
|
|
88
|
+
|
|
89
|
+
## Post-Category Relationship
|
|
90
|
+
|
|
91
|
+
Categories are linked to posts through a `post_categories` pivot table. When filtering posts by category:
|
|
92
|
+
|
|
93
|
+
1. Use the [Public Posts API](/api/v1/theme/blog/posts/public) with `category` query parameter
|
|
94
|
+
2. Query the pivot table directly for advanced filtering
|
|
95
|
+
|
|
96
|
+
## Features
|
|
97
|
+
|
|
98
|
+
- **Searchable**: name, description
|
|
99
|
+
- **Sortable**: All fields
|
|
100
|
+
- **Metadata**: Supported
|
|
101
|
+
|
|
102
|
+
## Permissions
|
|
103
|
+
|
|
104
|
+
- **Create/Update/Delete**: Owner only
|
|
105
|
+
|
|
106
|
+
## Error Responses
|
|
107
|
+
|
|
108
|
+
| Status | Description |
|
|
109
|
+
|--------|-------------|
|
|
110
|
+
| 400 | Bad Request - Invalid parameters |
|
|
111
|
+
| 401 | Unauthorized - Missing or invalid auth |
|
|
112
|
+
| 403 | Forbidden - Insufficient permissions |
|
|
113
|
+
| 404 | Not Found - Category doesn't exist |
|
|
114
|
+
| 422 | Validation Error - Invalid data |
|
|
115
|
+
|
|
116
|
+
## Related APIs
|
|
117
|
+
|
|
118
|
+
- **[Posts](/api/v1/posts)** - Blog posts
|
|
119
|
+
- **[Public Posts](/api/v1/theme/blog/posts/public)** - Filter by category
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Presets for Categories Entity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineApiEndpoint } from '@nextsparkjs/core/types/api-presets'
|
|
6
|
+
|
|
7
|
+
export default defineApiEndpoint({
|
|
8
|
+
summary: 'Manage blog categories for organizing posts',
|
|
9
|
+
presets: [
|
|
10
|
+
{
|
|
11
|
+
id: 'list-all',
|
|
12
|
+
title: 'List All Categories',
|
|
13
|
+
description: 'Get all categories with pagination',
|
|
14
|
+
method: 'GET',
|
|
15
|
+
params: {
|
|
16
|
+
limit: 50
|
|
17
|
+
},
|
|
18
|
+
tags: ['read', 'list']
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'search-by-name',
|
|
22
|
+
title: 'Search by Name',
|
|
23
|
+
description: 'Search categories by name',
|
|
24
|
+
method: 'GET',
|
|
25
|
+
params: {
|
|
26
|
+
search: '{{name}}'
|
|
27
|
+
},
|
|
28
|
+
tags: ['read', 'search']
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'create-category',
|
|
32
|
+
title: 'Create Category',
|
|
33
|
+
description: 'Create a new category',
|
|
34
|
+
method: 'POST',
|
|
35
|
+
payload: {
|
|
36
|
+
name: 'New Category',
|
|
37
|
+
slug: 'new-category',
|
|
38
|
+
description: 'Category description'
|
|
39
|
+
},
|
|
40
|
+
tags: ['write', 'create']
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'update-category',
|
|
44
|
+
title: 'Update Category',
|
|
45
|
+
description: 'Update an existing category',
|
|
46
|
+
method: 'PATCH',
|
|
47
|
+
pathParams: {
|
|
48
|
+
id: '{{id}}'
|
|
49
|
+
},
|
|
50
|
+
payload: {
|
|
51
|
+
name: '{{name}}',
|
|
52
|
+
description: '{{description}}'
|
|
53
|
+
},
|
|
54
|
+
tags: ['write', 'update']
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'delete-category',
|
|
58
|
+
title: 'Delete Category',
|
|
59
|
+
description: 'Delete a category record',
|
|
60
|
+
method: 'DELETE',
|
|
61
|
+
pathParams: {
|
|
62
|
+
id: '{{id}}'
|
|
63
|
+
},
|
|
64
|
+
tags: ['write', 'delete']
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
})
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Posts API
|
|
2
|
+
|
|
3
|
+
Manage blog posts with content builder, featured images, categories, and publication workflow.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Posts API allows you to create, read, update, and delete blog posts. Posts support a flexible content builder system, category organization, and a draft-to-published workflow.
|
|
8
|
+
|
|
9
|
+
## Authentication
|
|
10
|
+
|
|
11
|
+
All endpoints require authentication via:
|
|
12
|
+
- **Session cookie** (for browser-based requests)
|
|
13
|
+
- **API Key** header (for server-to-server requests)
|
|
14
|
+
|
|
15
|
+
**Note:** For public access to published posts, use the [Public Posts API](/api/v1/theme/blog/posts/public) instead.
|
|
16
|
+
|
|
17
|
+
## Endpoints
|
|
18
|
+
|
|
19
|
+
### List Posts
|
|
20
|
+
`GET /api/v1/posts`
|
|
21
|
+
|
|
22
|
+
Returns a paginated list of posts.
|
|
23
|
+
|
|
24
|
+
**Query Parameters:**
|
|
25
|
+
- `limit` (number, optional): Maximum records to return. Default: 20
|
|
26
|
+
- `offset` (number, optional): Number of records to skip. Default: 0
|
|
27
|
+
- `status` (string, optional): Filter by status (draft, published)
|
|
28
|
+
- `featured` (boolean, optional): Filter by featured flag
|
|
29
|
+
- `search` (string, optional): Search by title, excerpt, content
|
|
30
|
+
- `sortBy` (string, optional): Field to sort by
|
|
31
|
+
- `sortOrder` (string, optional): Sort direction (asc, desc)
|
|
32
|
+
|
|
33
|
+
**Example Response:**
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"data": [
|
|
37
|
+
{
|
|
38
|
+
"id": "post_abc123",
|
|
39
|
+
"title": "Getting Started with Next.js",
|
|
40
|
+
"slug": "getting-started-with-nextjs",
|
|
41
|
+
"excerpt": "A comprehensive guide to building modern web apps",
|
|
42
|
+
"content": "[{\"type\":\"text\",\"content\":\"...\"}]",
|
|
43
|
+
"featuredImage": "https://example.com/image.jpg",
|
|
44
|
+
"featured": true,
|
|
45
|
+
"status": "published",
|
|
46
|
+
"publishedAt": "2024-01-15T10:30:00Z",
|
|
47
|
+
"createdAt": "2024-01-10T08:00:00Z",
|
|
48
|
+
"updatedAt": "2024-01-15T10:30:00Z"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"pagination": {
|
|
52
|
+
"total": 25,
|
|
53
|
+
"limit": 20,
|
|
54
|
+
"offset": 0
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Get Single Post
|
|
60
|
+
`GET /api/v1/posts/[id]`
|
|
61
|
+
|
|
62
|
+
Returns a single post by ID.
|
|
63
|
+
|
|
64
|
+
### Create Post
|
|
65
|
+
`POST /api/v1/posts`
|
|
66
|
+
|
|
67
|
+
Create a new blog post.
|
|
68
|
+
|
|
69
|
+
**Request Body:**
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"title": "My New Post",
|
|
73
|
+
"slug": "my-new-post",
|
|
74
|
+
"excerpt": "A brief description of the post",
|
|
75
|
+
"content": "[{\"type\":\"text\",\"content\":\"Post content here\"}]",
|
|
76
|
+
"featuredImage": "https://example.com/image.jpg",
|
|
77
|
+
"featured": false,
|
|
78
|
+
"status": "draft"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Update Post
|
|
83
|
+
`PATCH /api/v1/posts/[id]`
|
|
84
|
+
|
|
85
|
+
Update an existing post. Supports partial updates.
|
|
86
|
+
|
|
87
|
+
### Publish Post
|
|
88
|
+
`PATCH /api/v1/posts/[id]`
|
|
89
|
+
|
|
90
|
+
Publish a draft post by setting status and publishedAt.
|
|
91
|
+
|
|
92
|
+
**Request Body:**
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"status": "published",
|
|
96
|
+
"publishedAt": "2024-01-15T10:30:00Z"
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Delete Post
|
|
101
|
+
`DELETE /api/v1/posts/[id]`
|
|
102
|
+
|
|
103
|
+
Delete a post record.
|
|
104
|
+
|
|
105
|
+
## Fields
|
|
106
|
+
|
|
107
|
+
| Field | Type | Required | Description |
|
|
108
|
+
|-------|------|----------|-------------|
|
|
109
|
+
| title | text | Yes | Post title |
|
|
110
|
+
| slug | text | Yes | URL-friendly slug (auto-generated if not provided) |
|
|
111
|
+
| excerpt | text | No | Brief description for previews |
|
|
112
|
+
| content | json | No | Page builder content blocks |
|
|
113
|
+
| featuredImage | url | No | Featured image URL |
|
|
114
|
+
| featured | boolean | No | Whether post is featured. Default: false |
|
|
115
|
+
| status | select | No | Status: draft, published. Default: draft |
|
|
116
|
+
| publishedAt | datetime | No | Publication date/time |
|
|
117
|
+
| createdAt | datetime | Auto | Creation timestamp |
|
|
118
|
+
| updatedAt | datetime | Auto | Last update timestamp |
|
|
119
|
+
|
|
120
|
+
## Content Builder
|
|
121
|
+
|
|
122
|
+
Posts use a flexible content builder system. The `content` field stores an array of content blocks in JSON format:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
[
|
|
126
|
+
{
|
|
127
|
+
"type": "text",
|
|
128
|
+
"content": "Paragraph content here..."
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"type": "heading",
|
|
132
|
+
"level": 2,
|
|
133
|
+
"content": "Section Title"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"type": "image",
|
|
137
|
+
"src": "https://example.com/image.jpg",
|
|
138
|
+
"alt": "Image description"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Categories
|
|
144
|
+
|
|
145
|
+
Posts can be organized into categories using a many-to-many relationship. Categories are managed via the `/api/v1/categories` endpoint and linked to posts through a pivot table.
|
|
146
|
+
|
|
147
|
+
To filter posts by category, use the Public Posts API with the `category` query parameter.
|
|
148
|
+
|
|
149
|
+
## Features
|
|
150
|
+
|
|
151
|
+
- **Searchable**: title, excerpt, content
|
|
152
|
+
- **Sortable**: Most fields
|
|
153
|
+
- **Public Access**: Published posts available via public API
|
|
154
|
+
- **Metadata**: Supported
|
|
155
|
+
|
|
156
|
+
## Permissions
|
|
157
|
+
|
|
158
|
+
- **Create/Update/Delete**: Owner only (single-user blog mode)
|
|
159
|
+
|
|
160
|
+
## Error Responses
|
|
161
|
+
|
|
162
|
+
| Status | Description |
|
|
163
|
+
|--------|-------------|
|
|
164
|
+
| 400 | Bad Request - Invalid parameters |
|
|
165
|
+
| 401 | Unauthorized - Missing or invalid auth |
|
|
166
|
+
| 403 | Forbidden - Insufficient permissions |
|
|
167
|
+
| 404 | Not Found - Post doesn't exist |
|
|
168
|
+
| 422 | Validation Error - Invalid data |
|
|
169
|
+
|
|
170
|
+
## Related APIs
|
|
171
|
+
|
|
172
|
+
- **[Categories](/api/v1/categories)** - Post categorization
|
|
173
|
+
- **[Public Posts](/api/v1/theme/blog/posts/public)** - Public feed (no auth required)
|
|
174
|
+
- **[Authors](/api/v1/theme/blog/authors)** - Author profiles
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Presets for Posts Entity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineApiEndpoint } from '@nextsparkjs/core/types/api-presets'
|
|
6
|
+
|
|
7
|
+
export default defineApiEndpoint({
|
|
8
|
+
summary: 'Manage blog posts with content builder and publication workflow',
|
|
9
|
+
presets: [
|
|
10
|
+
{
|
|
11
|
+
id: 'list-all',
|
|
12
|
+
title: 'List All Posts',
|
|
13
|
+
description: 'Get all posts with pagination',
|
|
14
|
+
method: 'GET',
|
|
15
|
+
params: {
|
|
16
|
+
limit: 20
|
|
17
|
+
},
|
|
18
|
+
tags: ['read', 'list']
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'list-published',
|
|
22
|
+
title: 'List Published Posts',
|
|
23
|
+
description: 'Get all published posts',
|
|
24
|
+
method: 'GET',
|
|
25
|
+
params: {
|
|
26
|
+
status: 'published'
|
|
27
|
+
},
|
|
28
|
+
tags: ['read', 'filter']
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'list-drafts',
|
|
32
|
+
title: 'List Draft Posts',
|
|
33
|
+
description: 'Get all draft posts',
|
|
34
|
+
method: 'GET',
|
|
35
|
+
params: {
|
|
36
|
+
status: 'draft'
|
|
37
|
+
},
|
|
38
|
+
tags: ['read', 'filter']
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'list-featured',
|
|
42
|
+
title: 'List Featured Posts',
|
|
43
|
+
description: 'Get all featured posts',
|
|
44
|
+
method: 'GET',
|
|
45
|
+
params: {
|
|
46
|
+
featured: 'true'
|
|
47
|
+
},
|
|
48
|
+
tags: ['read', 'filter']
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'search-by-title',
|
|
52
|
+
title: 'Search by Title',
|
|
53
|
+
description: 'Search posts by title',
|
|
54
|
+
method: 'GET',
|
|
55
|
+
params: {
|
|
56
|
+
search: '{{title}}'
|
|
57
|
+
},
|
|
58
|
+
tags: ['read', 'search']
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'create-draft',
|
|
62
|
+
title: 'Create Draft Post',
|
|
63
|
+
description: 'Create a new draft post',
|
|
64
|
+
method: 'POST',
|
|
65
|
+
payload: {
|
|
66
|
+
title: 'New Post Title',
|
|
67
|
+
slug: 'new-post-title',
|
|
68
|
+
excerpt: 'Brief description of the post',
|
|
69
|
+
status: 'draft'
|
|
70
|
+
},
|
|
71
|
+
tags: ['write', 'create']
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'publish-post',
|
|
75
|
+
title: 'Publish Post',
|
|
76
|
+
description: 'Publish a draft post',
|
|
77
|
+
method: 'PATCH',
|
|
78
|
+
pathParams: {
|
|
79
|
+
id: '{{id}}'
|
|
80
|
+
},
|
|
81
|
+
payload: {
|
|
82
|
+
status: 'published',
|
|
83
|
+
publishedAt: '{{publishedAt}}'
|
|
84
|
+
},
|
|
85
|
+
tags: ['write', 'update']
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'unpublish-post',
|
|
89
|
+
title: 'Unpublish Post',
|
|
90
|
+
description: 'Revert a post to draft',
|
|
91
|
+
method: 'PATCH',
|
|
92
|
+
pathParams: {
|
|
93
|
+
id: '{{id}}'
|
|
94
|
+
},
|
|
95
|
+
payload: {
|
|
96
|
+
status: 'draft'
|
|
97
|
+
},
|
|
98
|
+
tags: ['write', 'update']
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'set-featured',
|
|
102
|
+
title: 'Set as Featured',
|
|
103
|
+
description: 'Mark a post as featured',
|
|
104
|
+
method: 'PATCH',
|
|
105
|
+
pathParams: {
|
|
106
|
+
id: '{{id}}'
|
|
107
|
+
},
|
|
108
|
+
payload: {
|
|
109
|
+
featured: true
|
|
110
|
+
},
|
|
111
|
+
tags: ['write', 'update']
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'update-content',
|
|
115
|
+
title: 'Update Content',
|
|
116
|
+
description: 'Update post content blocks',
|
|
117
|
+
method: 'PATCH',
|
|
118
|
+
pathParams: {
|
|
119
|
+
id: '{{id}}'
|
|
120
|
+
},
|
|
121
|
+
payload: {
|
|
122
|
+
content: '{{content}}'
|
|
123
|
+
},
|
|
124
|
+
tags: ['write', 'update']
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'delete-post',
|
|
128
|
+
title: 'Delete Post',
|
|
129
|
+
description: 'Delete a post record',
|
|
130
|
+
method: 'DELETE',
|
|
131
|
+
pathParams: {
|
|
132
|
+
id: '{{id}}'
|
|
133
|
+
},
|
|
134
|
+
tags: ['write', 'delete']
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
})
|
package/lib/selectors.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Cypress tests (via tests/cypress/src/selectors.ts)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { createSelectorHelpers, CORE_SELECTORS } from '@nextsparkjs/
|
|
12
|
+
import { createSelectorHelpers, CORE_SELECTORS } from '@nextsparkjs/core/selectors'
|
|
13
13
|
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// BLOCK SELECTORS
|
|
@@ -174,5 +174,5 @@ export const entitySelectors = helpers.entitySelectors
|
|
|
174
174
|
export type ThemeSelectorsType = typeof THEME_SELECTORS
|
|
175
175
|
export type BlockSelectorsType = typeof BLOCK_SELECTORS
|
|
176
176
|
export type EntitySelectorsType = typeof ENTITY_SELECTORS
|
|
177
|
-
export type { Replacements } from '@nextsparkjs/
|
|
177
|
+
export type { Replacements } from '@nextsparkjs/core/selectors'
|
|
178
178
|
export { CORE_SELECTORS }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/theme-blog",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.45",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./config/theme.config.ts",
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"react": "^19.0.0",
|
|
14
14
|
"react-dom": "^19.0.0",
|
|
15
15
|
"zod": "^4.0.0",
|
|
16
|
-
"@nextsparkjs/core": "0.1.0-beta.
|
|
17
|
-
"@nextsparkjs/testing": "0.1.0-beta.
|
|
16
|
+
"@nextsparkjs/core": "0.1.0-beta.45",
|
|
17
|
+
"@nextsparkjs/testing": "0.1.0-beta.45"
|
|
18
18
|
},
|
|
19
19
|
"nextspark": {
|
|
20
20
|
"type": "theme",
|
package/styles/globals.css
CHANGED
|
@@ -3,8 +3,23 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Warm amber/orange palette with editorial typography.
|
|
5
5
|
* Fonts: Oxanium (sans), Merriweather (serif), Fira Code (mono)
|
|
6
|
+
*
|
|
7
|
+
* ÚNICA FUENTE DE VERDAD para estilos del theme.
|
|
8
|
+
* Editar este archivo para customizar el design system.
|
|
6
9
|
*/
|
|
7
10
|
|
|
11
|
+
/* =============================================
|
|
12
|
+
IMPORTS
|
|
13
|
+
============================================= */
|
|
14
|
+
@import "tailwindcss";
|
|
15
|
+
@import "@nextsparkjs/core/styles/ui.css";
|
|
16
|
+
@import "@nextsparkjs/core/styles/utilities.css";
|
|
17
|
+
@import "@nextsparkjs/core/styles/docs.css";
|
|
18
|
+
@source "../../../**/*.{js,ts,jsx,tsx}";
|
|
19
|
+
|
|
20
|
+
/* =============================================
|
|
21
|
+
BLOG THEME - LIGHT MODE
|
|
22
|
+
============================================= */
|
|
8
23
|
:root {
|
|
9
24
|
--background: oklch(0.9885 0.0057 84.5659);
|
|
10
25
|
--foreground: oklch(0.3660 0.0251 49.6085);
|
|
@@ -152,6 +167,18 @@
|
|
|
152
167
|
--shadow-2xl: var(--shadow-2xl);
|
|
153
168
|
}
|
|
154
169
|
|
|
170
|
+
/* =============================================
|
|
171
|
+
BASE STYLES
|
|
172
|
+
============================================= */
|
|
173
|
+
* {
|
|
174
|
+
border-color: var(--border);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
body {
|
|
178
|
+
background-color: var(--background);
|
|
179
|
+
color: var(--foreground);
|
|
180
|
+
}
|
|
181
|
+
|
|
155
182
|
/* ============================================================================
|
|
156
183
|
TYPOGRAPHY
|
|
157
184
|
============================================================================ */
|