jpie 0.4.0 → 0.4.2
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.
- checksums.yaml +4 -4
- data/{.aiconfig → .cursorrules} +21 -5
- data/.overcommit.yml +35 -0
- data/CHANGELOG.md +33 -0
- data/README.md +97 -1063
- data/examples/basic_example.md +146 -0
- data/examples/including_related_resources.md +491 -0
- data/examples/pagination.md +303 -0
- data/examples/resource_attribute_configuration.md +147 -0
- data/examples/resource_meta_configuration.md +244 -0
- data/examples/single_table_inheritance.md +160 -0
- data/lib/jpie/controller/crud_actions.rb +33 -2
- data/lib/jpie/controller/error_handling/handler_setup.rb +124 -0
- data/lib/jpie/controller/error_handling/handlers.rb +109 -0
- data/lib/jpie/controller/error_handling.rb +10 -28
- data/lib/jpie/controller/json_api_validation.rb +193 -0
- data/lib/jpie/controller/parameter_parsing.rb +43 -0
- data/lib/jpie/controller/rendering.rb +95 -1
- data/lib/jpie/controller.rb +2 -0
- data/lib/jpie/errors.rb +41 -0
- data/lib/jpie/resource/attributable.rb +16 -2
- data/lib/jpie/resource.rb +40 -0
- data/lib/jpie/version.rb +1 -1
- metadata +13 -3
@@ -0,0 +1,303 @@
|
|
1
|
+
# Pagination Example
|
2
|
+
|
3
|
+
This example demonstrates how to implement pagination with JPie resources using both simple and JSON:API standard pagination parameters.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
### 1. Model (`app/models/article.rb`)
|
8
|
+
```ruby
|
9
|
+
class Article < ActiveRecord::Base
|
10
|
+
validates :title, presence: true
|
11
|
+
validates :content, presence: true
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
### 2. Resource (`app/resources/article_resource.rb`)
|
16
|
+
```ruby
|
17
|
+
class ArticleResource < JPie::Resource
|
18
|
+
attributes :title, :content, :published_at
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
### 3. Controller (`app/controllers/articles_controller.rb`)
|
23
|
+
```ruby
|
24
|
+
class ArticlesController < ApplicationController
|
25
|
+
include JPie::Controller
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
### 4. Routes (`config/routes.rb`)
|
30
|
+
```ruby
|
31
|
+
Rails.application.routes.draw do
|
32
|
+
resources :articles
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
## HTTP Examples
|
37
|
+
|
38
|
+
### Simple Pagination Parameters
|
39
|
+
|
40
|
+
#### Get First Page with 5 Articles
|
41
|
+
```http
|
42
|
+
GET /articles?page=1&per_page=5
|
43
|
+
Accept: application/vnd.api+json
|
44
|
+
|
45
|
+
HTTP/1.1 200 OK
|
46
|
+
Content-Type: application/vnd.api+json
|
47
|
+
|
48
|
+
{
|
49
|
+
"data": [
|
50
|
+
{
|
51
|
+
"id": "1",
|
52
|
+
"type": "articles",
|
53
|
+
"attributes": {
|
54
|
+
"title": "First Article",
|
55
|
+
"content": "Content of first article",
|
56
|
+
"published_at": "2024-01-01T10:00:00Z"
|
57
|
+
}
|
58
|
+
},
|
59
|
+
{
|
60
|
+
"id": "2",
|
61
|
+
"type": "articles",
|
62
|
+
"attributes": {
|
63
|
+
"title": "Second Article",
|
64
|
+
"content": "Content of second article",
|
65
|
+
"published_at": "2024-01-02T10:00:00Z"
|
66
|
+
}
|
67
|
+
}
|
68
|
+
],
|
69
|
+
"meta": {
|
70
|
+
"pagination": {
|
71
|
+
"page": 1,
|
72
|
+
"per_page": 5,
|
73
|
+
"total_pages": 4,
|
74
|
+
"total_count": 20
|
75
|
+
}
|
76
|
+
},
|
77
|
+
"links": {
|
78
|
+
"self": "http://example.com/articles?page=1&per_page=5",
|
79
|
+
"first": "http://example.com/articles?page=1&per_page=5",
|
80
|
+
"last": "http://example.com/articles?page=4&per_page=5",
|
81
|
+
"next": "http://example.com/articles?page=2&per_page=5"
|
82
|
+
}
|
83
|
+
}
|
84
|
+
```
|
85
|
+
|
86
|
+
#### Get Second Page
|
87
|
+
```http
|
88
|
+
GET /articles?page=2&per_page=5
|
89
|
+
Accept: application/vnd.api+json
|
90
|
+
|
91
|
+
HTTP/1.1 200 OK
|
92
|
+
Content-Type: application/vnd.api+json
|
93
|
+
|
94
|
+
{
|
95
|
+
"data": [
|
96
|
+
{
|
97
|
+
"id": "6",
|
98
|
+
"type": "articles",
|
99
|
+
"attributes": {
|
100
|
+
"title": "Sixth Article",
|
101
|
+
"content": "Content of sixth article",
|
102
|
+
"published_at": "2024-01-06T10:00:00Z"
|
103
|
+
}
|
104
|
+
}
|
105
|
+
],
|
106
|
+
"meta": {
|
107
|
+
"pagination": {
|
108
|
+
"page": 2,
|
109
|
+
"per_page": 5,
|
110
|
+
"total_pages": 4,
|
111
|
+
"total_count": 20
|
112
|
+
}
|
113
|
+
},
|
114
|
+
"links": {
|
115
|
+
"self": "http://example.com/articles?page=2&per_page=5",
|
116
|
+
"first": "http://example.com/articles?page=1&per_page=5",
|
117
|
+
"last": "http://example.com/articles?page=4&per_page=5",
|
118
|
+
"prev": "http://example.com/articles?page=1&per_page=5",
|
119
|
+
"next": "http://example.com/articles?page=3&per_page=5"
|
120
|
+
}
|
121
|
+
}
|
122
|
+
```
|
123
|
+
|
124
|
+
### JSON:API Standard Pagination Parameters
|
125
|
+
|
126
|
+
#### Get First Page Using JSON:API Format
|
127
|
+
```http
|
128
|
+
GET /articles?page[number]=1&page[size]=3
|
129
|
+
Accept: application/vnd.api+json
|
130
|
+
|
131
|
+
HTTP/1.1 200 OK
|
132
|
+
Content-Type: application/vnd.api+json
|
133
|
+
|
134
|
+
{
|
135
|
+
"data": [
|
136
|
+
{
|
137
|
+
"id": "1",
|
138
|
+
"type": "articles",
|
139
|
+
"attributes": {
|
140
|
+
"title": "First Article",
|
141
|
+
"content": "Content of first article",
|
142
|
+
"published_at": "2024-01-01T10:00:00Z"
|
143
|
+
}
|
144
|
+
},
|
145
|
+
{
|
146
|
+
"id": "2",
|
147
|
+
"type": "articles",
|
148
|
+
"attributes": {
|
149
|
+
"title": "Second Article",
|
150
|
+
"content": "Content of second article",
|
151
|
+
"published_at": "2024-01-02T10:00:00Z"
|
152
|
+
}
|
153
|
+
},
|
154
|
+
{
|
155
|
+
"id": "3",
|
156
|
+
"type": "articles",
|
157
|
+
"attributes": {
|
158
|
+
"title": "Third Article",
|
159
|
+
"content": "Content of third article",
|
160
|
+
"published_at": "2024-01-03T10:00:00Z"
|
161
|
+
}
|
162
|
+
}
|
163
|
+
],
|
164
|
+
"meta": {
|
165
|
+
"pagination": {
|
166
|
+
"page": 1,
|
167
|
+
"per_page": 3,
|
168
|
+
"total_pages": 7,
|
169
|
+
"total_count": 20
|
170
|
+
}
|
171
|
+
},
|
172
|
+
"links": {
|
173
|
+
"self": "http://example.com/articles?page=1&per_page=3",
|
174
|
+
"first": "http://example.com/articles?page=1&per_page=3",
|
175
|
+
"last": "http://example.com/articles?page=7&per_page=3",
|
176
|
+
"next": "http://example.com/articles?page=2&per_page=3"
|
177
|
+
}
|
178
|
+
}
|
179
|
+
```
|
180
|
+
|
181
|
+
### Pagination with Sorting
|
182
|
+
|
183
|
+
#### Get Sorted and Paginated Results
|
184
|
+
```http
|
185
|
+
GET /articles?sort=-published_at&page=1&per_page=3
|
186
|
+
Accept: application/vnd.api+json
|
187
|
+
|
188
|
+
HTTP/1.1 200 OK
|
189
|
+
Content-Type: application/vnd.api+json
|
190
|
+
|
191
|
+
{
|
192
|
+
"data": [
|
193
|
+
{
|
194
|
+
"id": "20",
|
195
|
+
"type": "articles",
|
196
|
+
"attributes": {
|
197
|
+
"title": "Latest Article",
|
198
|
+
"content": "Most recent content",
|
199
|
+
"published_at": "2024-01-20T10:00:00Z"
|
200
|
+
}
|
201
|
+
},
|
202
|
+
{
|
203
|
+
"id": "19",
|
204
|
+
"type": "articles",
|
205
|
+
"attributes": {
|
206
|
+
"title": "Second Latest Article",
|
207
|
+
"content": "Second most recent content",
|
208
|
+
"published_at": "2024-01-19T10:00:00Z"
|
209
|
+
}
|
210
|
+
},
|
211
|
+
{
|
212
|
+
"id": "18",
|
213
|
+
"type": "articles",
|
214
|
+
"attributes": {
|
215
|
+
"title": "Third Latest Article",
|
216
|
+
"content": "Third most recent content",
|
217
|
+
"published_at": "2024-01-18T10:00:00Z"
|
218
|
+
}
|
219
|
+
}
|
220
|
+
],
|
221
|
+
"meta": {
|
222
|
+
"pagination": {
|
223
|
+
"page": 1,
|
224
|
+
"per_page": 3,
|
225
|
+
"total_pages": 7,
|
226
|
+
"total_count": 20
|
227
|
+
}
|
228
|
+
},
|
229
|
+
"links": {
|
230
|
+
"self": "http://example.com/articles?sort=-published_at&page=1&per_page=3",
|
231
|
+
"first": "http://example.com/articles?sort=-published_at&page=1&per_page=3",
|
232
|
+
"last": "http://example.com/articles?sort=-published_at&page=7&per_page=3",
|
233
|
+
"next": "http://example.com/articles?sort=-published_at&page=2&per_page=3"
|
234
|
+
}
|
235
|
+
}
|
236
|
+
```
|
237
|
+
|
238
|
+
### Last Page Response
|
239
|
+
|
240
|
+
#### Get Last Page
|
241
|
+
```http
|
242
|
+
GET /articles?page=4&per_page=5
|
243
|
+
Accept: application/vnd.api+json
|
244
|
+
|
245
|
+
HTTP/1.1 200 OK
|
246
|
+
Content-Type: application/vnd.api+json
|
247
|
+
|
248
|
+
{
|
249
|
+
"data": [
|
250
|
+
{
|
251
|
+
"id": "20",
|
252
|
+
"type": "articles",
|
253
|
+
"attributes": {
|
254
|
+
"title": "Last Article",
|
255
|
+
"content": "Content of last article",
|
256
|
+
"published_at": "2024-01-20T10:00:00Z"
|
257
|
+
}
|
258
|
+
}
|
259
|
+
],
|
260
|
+
"meta": {
|
261
|
+
"pagination": {
|
262
|
+
"page": 4,
|
263
|
+
"per_page": 5,
|
264
|
+
"total_pages": 4,
|
265
|
+
"total_count": 20
|
266
|
+
}
|
267
|
+
},
|
268
|
+
"links": {
|
269
|
+
"self": "http://example.com/articles?page=4&per_page=5",
|
270
|
+
"first": "http://example.com/articles?page=1&per_page=5",
|
271
|
+
"last": "http://example.com/articles?page=4&per_page=5",
|
272
|
+
"prev": "http://example.com/articles?page=3&per_page=5"
|
273
|
+
}
|
274
|
+
}
|
275
|
+
```
|
276
|
+
|
277
|
+
### Empty Results
|
278
|
+
|
279
|
+
#### Get Page Beyond Available Data
|
280
|
+
```http
|
281
|
+
GET /articles?page=10&per_page=5
|
282
|
+
Accept: application/vnd.api+json
|
283
|
+
|
284
|
+
HTTP/1.1 200 OK
|
285
|
+
Content-Type: application/vnd.api+json
|
286
|
+
|
287
|
+
{
|
288
|
+
"data": [],
|
289
|
+
"meta": {
|
290
|
+
"pagination": {
|
291
|
+
"page": 10,
|
292
|
+
"per_page": 5,
|
293
|
+
"total_pages": 4,
|
294
|
+
"total_count": 20
|
295
|
+
}
|
296
|
+
},
|
297
|
+
"links": {
|
298
|
+
"self": "http://example.com/articles?page=10&per_page=5",
|
299
|
+
"first": "http://example.com/articles?page=1&per_page=5",
|
300
|
+
"last": "http://example.com/articles?page=4&per_page=5"
|
301
|
+
}
|
302
|
+
}
|
303
|
+
```
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# Resource Attribute Configuration Example
|
2
|
+
|
3
|
+
This example demonstrates all the different ways to configure resource attributes in JPie, showcasing the various configuration patterns and customization techniques available for attributes.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
### 1. Model (`app/models/user.rb`)
|
8
|
+
```ruby
|
9
|
+
class User < ActiveRecord::Base
|
10
|
+
validates :first_name, presence: true
|
11
|
+
validates :last_name, presence: true
|
12
|
+
validates :email, presence: true, uniqueness: true
|
13
|
+
validates :username, presence: true, uniqueness: true
|
14
|
+
|
15
|
+
def active?
|
16
|
+
true # Simple boolean for demonstration
|
17
|
+
end
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
### 2. Resource with All Attribute Configuration Types (`app/resources/user_resource.rb`)
|
22
|
+
```ruby
|
23
|
+
class UserResource < JPie::Resource
|
24
|
+
# 1. Basic attributes - direct model attribute access
|
25
|
+
attributes :email, :first_name, :last_name
|
26
|
+
|
27
|
+
# 2. Attribute with custom attr mapping (maps to different model attribute)
|
28
|
+
attribute :display_name, attr: :username
|
29
|
+
|
30
|
+
# 3. Attribute with block override
|
31
|
+
attribute :status do
|
32
|
+
object.active? ? 'active' : 'inactive'
|
33
|
+
end
|
34
|
+
|
35
|
+
# 4. Attribute with custom method override
|
36
|
+
attribute :full_name
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Custom method for attribute override
|
41
|
+
def full_name
|
42
|
+
"#{object.first_name} #{object.last_name}".strip
|
43
|
+
end
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
### 3. Controller (`app/controllers/users_controller.rb`)
|
48
|
+
```ruby
|
49
|
+
class UsersController < ApplicationController
|
50
|
+
include JPie::Controller
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
## HTTP Examples
|
55
|
+
|
56
|
+
### Create User
|
57
|
+
```http
|
58
|
+
POST /users
|
59
|
+
Content-Type: application/vnd.api+json
|
60
|
+
|
61
|
+
{
|
62
|
+
"data": {
|
63
|
+
"type": "users",
|
64
|
+
"attributes": {
|
65
|
+
"email": "john.doe@example.com",
|
66
|
+
"first_name": "John",
|
67
|
+
"last_name": "Doe"
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
HTTP/1.1 201 Created
|
73
|
+
Content-Type: application/vnd.api+json
|
74
|
+
|
75
|
+
{
|
76
|
+
"data": {
|
77
|
+
"id": "1",
|
78
|
+
"type": "users",
|
79
|
+
"attributes": {
|
80
|
+
"email": "john.doe@example.com",
|
81
|
+
"first_name": "John",
|
82
|
+
"last_name": "Doe",
|
83
|
+
"display_name": "johndoe",
|
84
|
+
"status": "active",
|
85
|
+
"full_name": "John Doe"
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
```
|
90
|
+
|
91
|
+
### Update User
|
92
|
+
```http
|
93
|
+
PATCH /users/1
|
94
|
+
Content-Type: application/vnd.api+json
|
95
|
+
|
96
|
+
{
|
97
|
+
"data": {
|
98
|
+
"id": "1",
|
99
|
+
"type": "users",
|
100
|
+
"attributes": {
|
101
|
+
"first_name": "Jonathan"
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
HTTP/1.1 200 OK
|
107
|
+
Content-Type: application/vnd.api+json
|
108
|
+
|
109
|
+
{
|
110
|
+
"data": {
|
111
|
+
"id": "1",
|
112
|
+
"type": "users",
|
113
|
+
"attributes": {
|
114
|
+
"email": "john.doe@example.com",
|
115
|
+
"first_name": "Jonathan",
|
116
|
+
"last_name": "Doe",
|
117
|
+
"display_name": "johndoe",
|
118
|
+
"status": "active",
|
119
|
+
"full_name": "Jonathan Doe"
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
```
|
124
|
+
|
125
|
+
### Get User with All Attribute Configuration Types
|
126
|
+
```http
|
127
|
+
GET /users/1
|
128
|
+
Accept: application/vnd.api+json
|
129
|
+
|
130
|
+
HTTP/1.1 200 OK
|
131
|
+
Content-Type: application/vnd.api+json
|
132
|
+
|
133
|
+
{
|
134
|
+
"data": {
|
135
|
+
"id": "1",
|
136
|
+
"type": "users",
|
137
|
+
"attributes": {
|
138
|
+
"email": "john.doe@example.com",
|
139
|
+
"first_name": "John",
|
140
|
+
"last_name": "Doe",
|
141
|
+
"display_name": "johndoe",
|
142
|
+
"status": "active",
|
143
|
+
"full_name": "John Doe"
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
```
|
@@ -0,0 +1,244 @@
|
|
1
|
+
# Meta Field Configuration Example
|
2
|
+
|
3
|
+
This example demonstrates all the different ways to define and configure meta fields in JPie, focusing on the various configuration patterns and syntax options available.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
### 1. Article model
|
8
|
+
```ruby
|
9
|
+
class Article < ActiveRecord::Base
|
10
|
+
validates :title, presence: true
|
11
|
+
validates :content, presence: true
|
12
|
+
validates :status, inclusion: { in: %w[draft published archived] }
|
13
|
+
|
14
|
+
belongs_to :author, class_name: 'User'
|
15
|
+
|
16
|
+
def word_count
|
17
|
+
content.split.length
|
18
|
+
end
|
19
|
+
|
20
|
+
def reading_time_minutes
|
21
|
+
(word_count / 200.0).ceil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
### 2. Resource with All Meta Field Configuration Types
|
27
|
+
```ruby
|
28
|
+
class ArticleResource < JPie::Resource
|
29
|
+
# 1. Basic meta attributes - direct model access and custom methods
|
30
|
+
meta_attributes :created_at, :updated_at
|
31
|
+
meta_attribute :reading_time
|
32
|
+
|
33
|
+
# 2. Meta attribute with attr mapping (maps to different model attribute)
|
34
|
+
meta_attribute :author_name, attr: :author_email
|
35
|
+
|
36
|
+
# 3. Meta attribute with block (legacy style)
|
37
|
+
meta_attribute :word_count do
|
38
|
+
object.word_count
|
39
|
+
end
|
40
|
+
|
41
|
+
# 4. Meta attribute with proc block (alternative legacy style)
|
42
|
+
meta_attribute :character_count, block: proc { object.content.length }
|
43
|
+
|
44
|
+
# 5. Short alias syntax (modern style)
|
45
|
+
meta :api_version
|
46
|
+
metas :request_id, :cache_key
|
47
|
+
|
48
|
+
# 6. Custom meta method for dynamic metadata
|
49
|
+
def meta
|
50
|
+
# Start with declared meta attributes
|
51
|
+
base_meta = super
|
52
|
+
|
53
|
+
# Add dynamic metadata
|
54
|
+
dynamic_meta = {
|
55
|
+
timestamp: Time.current.iso8601,
|
56
|
+
resource_version: '2.1'
|
57
|
+
}
|
58
|
+
|
59
|
+
# Conditional metadata based on context
|
60
|
+
if context[:include_debug]
|
61
|
+
dynamic_meta[:debug_info] = {
|
62
|
+
object_class: object.class.name,
|
63
|
+
context_keys: context.keys
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
# Merge and return
|
68
|
+
base_meta.merge(dynamic_meta)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Custom method for reading_time meta attribute
|
74
|
+
def reading_time
|
75
|
+
{
|
76
|
+
minutes: object.reading_time_minutes,
|
77
|
+
formatted: "#{object.reading_time_minutes} min read"
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
# Meta attribute accessing context
|
82
|
+
def user_role
|
83
|
+
context[:current_user]&.role || 'anonymous'
|
84
|
+
end
|
85
|
+
|
86
|
+
# Meta attribute with conditional logic
|
87
|
+
def edit_permissions
|
88
|
+
current_user = context[:current_user]
|
89
|
+
return false unless current_user
|
90
|
+
|
91
|
+
current_user.admin? || current_user == object.author
|
92
|
+
end
|
93
|
+
|
94
|
+
# Meta attributes using short alias syntax
|
95
|
+
def api_version
|
96
|
+
'1.0'
|
97
|
+
end
|
98
|
+
|
99
|
+
def request_id
|
100
|
+
context[:request_id] || SecureRandom.uuid
|
101
|
+
end
|
102
|
+
|
103
|
+
def cache_key
|
104
|
+
"article:#{object.id}:#{object.updated_at.to_i}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
## HTTP Examples
|
110
|
+
|
111
|
+
### Create Article
|
112
|
+
```http
|
113
|
+
POST /articles
|
114
|
+
Content-Type: application/vnd.api+json
|
115
|
+
Authorization: Bearer user_token
|
116
|
+
|
117
|
+
{
|
118
|
+
"data": {
|
119
|
+
"type": "articles",
|
120
|
+
"attributes": {
|
121
|
+
"title": "Meta Field Configuration Guide",
|
122
|
+
"content": "This guide demonstrates all the different ways to configure meta fields in JPie...",
|
123
|
+
"status": "draft"
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
HTTP/1.1 201 Created
|
129
|
+
Content-Type: application/vnd.api+json
|
130
|
+
|
131
|
+
{
|
132
|
+
"data": {
|
133
|
+
"id": "1",
|
134
|
+
"type": "articles",
|
135
|
+
"attributes": {
|
136
|
+
"title": "Meta Field Configuration Guide",
|
137
|
+
"content": "This guide demonstrates all the different ways to configure meta fields in JPie...",
|
138
|
+
"status": "draft"
|
139
|
+
},
|
140
|
+
"meta": {
|
141
|
+
"created_at": "2024-01-15T10:30:00Z",
|
142
|
+
"updated_at": "2024-01-15T10:30:00Z",
|
143
|
+
"reading_time": {
|
144
|
+
"minutes": 3,
|
145
|
+
"formatted": "3 min read"
|
146
|
+
},
|
147
|
+
"author_name": "john@example.com",
|
148
|
+
"word_count": 450,
|
149
|
+
"character_count": 2700,
|
150
|
+
"api_version": "1.0",
|
151
|
+
"request_id": "req_abc123def456",
|
152
|
+
"cache_key": "article:1:1705492800",
|
153
|
+
"timestamp": "2024-03-15T16:45:30Z",
|
154
|
+
"resource_version": "2.1"
|
155
|
+
}
|
156
|
+
}
|
157
|
+
}
|
158
|
+
```
|
159
|
+
|
160
|
+
### Update Article
|
161
|
+
```http
|
162
|
+
PATCH /articles/1
|
163
|
+
Content-Type: application/vnd.api+json
|
164
|
+
Authorization: Bearer user_token
|
165
|
+
|
166
|
+
{
|
167
|
+
"data": {
|
168
|
+
"id": "1",
|
169
|
+
"type": "articles",
|
170
|
+
"attributes": {
|
171
|
+
"status": "published"
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
HTTP/1.1 200 OK
|
177
|
+
Content-Type: application/vnd.api+json
|
178
|
+
|
179
|
+
{
|
180
|
+
"data": {
|
181
|
+
"id": "1",
|
182
|
+
"type": "articles",
|
183
|
+
"attributes": {
|
184
|
+
"title": "Meta Field Configuration Guide",
|
185
|
+
"content": "This guide demonstrates all the different ways to configure meta fields in JPie...",
|
186
|
+
"status": "published"
|
187
|
+
},
|
188
|
+
"meta": {
|
189
|
+
"created_at": "2024-01-15T10:30:00Z",
|
190
|
+
"updated_at": "2024-01-16T14:20:00Z",
|
191
|
+
"reading_time": {
|
192
|
+
"minutes": 3,
|
193
|
+
"formatted": "3 min read"
|
194
|
+
},
|
195
|
+
"author_name": "john@example.com",
|
196
|
+
"word_count": 450,
|
197
|
+
"character_count": 2700,
|
198
|
+
"api_version": "1.0",
|
199
|
+
"request_id": "req_xyz789ghi012",
|
200
|
+
"cache_key": "article:1:1705492800",
|
201
|
+
"timestamp": "2024-03-16T14:20:00Z",
|
202
|
+
"resource_version": "2.1"
|
203
|
+
}
|
204
|
+
}
|
205
|
+
}
|
206
|
+
```
|
207
|
+
|
208
|
+
### Get Article with All Meta Field Configurations
|
209
|
+
```http
|
210
|
+
GET /articles/1
|
211
|
+
Accept: application/vnd.api+json
|
212
|
+
Authorization: Bearer user_token
|
213
|
+
|
214
|
+
HTTP/1.1 200 OK
|
215
|
+
Content-Type: application/vnd.api+json
|
216
|
+
|
217
|
+
{
|
218
|
+
"data": {
|
219
|
+
"id": "1",
|
220
|
+
"type": "articles",
|
221
|
+
"attributes": {
|
222
|
+
"title": "Meta Field Configuration Guide",
|
223
|
+
"content": "This guide demonstrates all the different ways to configure meta fields in JPie...",
|
224
|
+
"status": "published"
|
225
|
+
},
|
226
|
+
"meta": {
|
227
|
+
"created_at": "2024-01-15T10:30:00Z",
|
228
|
+
"updated_at": "2024-01-16T14:20:00Z",
|
229
|
+
"reading_time": {
|
230
|
+
"minutes": 3,
|
231
|
+
"formatted": "3 min read"
|
232
|
+
},
|
233
|
+
"author_name": "john@example.com",
|
234
|
+
"word_count": 450,
|
235
|
+
"character_count": 2700,
|
236
|
+
"api_version": "1.0",
|
237
|
+
"request_id": "req_abc123def456",
|
238
|
+
"cache_key": "article:1:1705492800",
|
239
|
+
"timestamp": "2024-03-16T14:20:00Z",
|
240
|
+
"resource_version": "2.1"
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
```
|