Skip to content

Commit a3598df

Browse files
Add Posts API documentation
- Created `Docs/Posts.md` to detail the domain model, HTTP API, and endpoints for Posts in `sparkly-server`. - Included usage examples, permission rules, DTO shapes, and error formats. - Added guidelines for maintainers to ensure
1 parent 76bd079 commit a3598df

File tree

1 file changed

+281
-0
lines changed

1 file changed

+281
-0
lines changed

Docs/Posts.md

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# Posts API Documentation
2+
3+
Posts are short updates attached to a project. They are used to document progress, share context, and keep a public "build in public" log inside Sparkly.
4+
5+
This document describes the domain model and HTTP API for managing posts in `sparkly-server`.
6+
7+
## Domain model
8+
9+
A `Post` represents a single update written by a user, optionally attached to a project.
10+
11+
**Fields**
12+
13+
* `Id` (`Guid`): Unique identifier of the post.
14+
* `AuthorId` (`Guid`): Id of the user who created the post.
15+
* `ProjectId` (`Guid?`): Id of the project this post belongs to. Nullable for potential future global posts.
16+
* `Title` (`string`): Short title or headline of the post.
17+
* `Content` (`string`): Main text body of the post (Markdown-friendly).
18+
* `CreatedAt` (`DateTime`): UTC timestamp when the post was created.
19+
* `UpdatedAt` (`DateTime`): UTC timestamp of the last modification.
20+
21+
**Invariants and behaviour**
22+
23+
* A post must always have a non-empty `Title` and `Content`.
24+
* `CreatedAt` is set once at creation time.
25+
* Every modification calls the internal `Touch()` method, which updates `UpdatedAt` to current UTC time.
26+
* Posts for a project are conceptually ordered by `CreatedAt` from newest to oldest.
27+
28+
## Permissions and authorization
29+
30+
* All endpoints require an authenticated user.
31+
* `AuthorId` is always taken from the authenticated user context; the client does not choose it.
32+
* Only the author (and in the future possibly project owners / admins) may edit or delete a post.
33+
* Read access rules:
34+
35+
* Project members can read all posts for that project.
36+
* Public projects may expose posts publicly via separate public endpoints in the future.
37+
38+
The API exposes basic permission flags in the response DTO:
39+
40+
* `CanEdit` (`bool`): `true` if the current user is allowed to edit the post.
41+
* `CanDelete` (`bool`): `true` if the current user is allowed to delete the post.
42+
43+
These flags are computed server-side based on the current user, not stored in the database.
44+
45+
## DTO: PostResponse
46+
47+
`PostResponse` is returned by all read endpoints.
48+
49+
C# shape (conceptual):
50+
51+
```csharp
52+
public sealed class PostResponse
53+
{
54+
public Guid Id { get; init; }
55+
public Guid ProjectId { get; init; }
56+
public Guid AuthorId { get; init; }
57+
public string Title { get; init; } = default!;
58+
public string Content { get; init; } = default!;
59+
public DateTime CreatedAt { get; init; }
60+
public DateTime UpdatedAt { get; init; }
61+
public bool CanEdit { get; init; }
62+
public bool CanDelete { get; init; }
63+
}
64+
```
65+
66+
JSON representation (using the global JSON naming policy; here `camelCase` is assumed):
67+
68+
```json
69+
{
70+
"id": "d7c88f39-2f8b-4b10-8f10-5b93dd0b5c34",
71+
"projectId": "a3f9d5e2-91b0-4b5a-9530-7f9a6a7a2510",
72+
"authorId": "4a2537d5-2a7b-4b17-b1a4-2a0fae7b02a1",
73+
"title": "Shipped the first version of the auth flow",
74+
"content": "Today I implemented refresh tokens and wired them into the frontend.",
75+
"createdAt": "2025-11-29T18:30:00Z",
76+
"updatedAt": "2025-11-29T18:35:12Z",
77+
"canEdit": true,
78+
"canDelete": true
79+
}
80+
```
81+
82+
## Base route
83+
84+
All endpoints in this document assume the following controller route:
85+
86+
```text
87+
/api/v1/projects/{projectId:guid}/posts
88+
```
89+
90+
* `projectId` – the `Guid` of the project the posts belong to.
91+
92+
If the actual route in `PostsController` differs, adapt the paths below accordingly.
93+
94+
## Endpoints
95+
96+
### 1. List posts for a project
97+
98+
Returns all posts for the given project, ordered from newest to oldest.
99+
100+
* **Method:** `GET`
101+
* **URL:** `/api/v1/projects/{projectId}/posts`
102+
* **Auth:** required (Bearer token)
103+
104+
**Path parameters**
105+
106+
* `projectId` (`Guid`) – id of the project.
107+
108+
**Query parameters**
109+
110+
Currently none. Pagination can be added later (for example `page`, `pageSize`).
111+
112+
**Responses**
113+
114+
* `200 OK` – list of posts.
115+
116+
Example response:
117+
118+
```json
119+
[
120+
{
121+
"id": "d7c88f39-2f8b-4b10-8f10-5b93dd0b5c34",
122+
"projectId": "a3f9d5e2-91b0-4b5a-9530-7f9a6a7a2510",
123+
"authorId": "4a2537d5-2a7b-4b17-b1a4-2a0fae7b02a1",
124+
"title": "Day 3 – basic dashboard UI",
125+
"content": "Implemented the first version of the dashboard and wired it to the API.",
126+
"createdAt": "2025-11-28T10:21:00Z",
127+
"updatedAt": "2025-11-28T10:21:00Z",
128+
"canEdit": true,
129+
"canDelete": true
130+
}
131+
]
132+
```
133+
134+
* `404 Not Found` – if the project does not exist or is not visible to the current user.
135+
136+
---
137+
138+
### 2. Create a new post
139+
140+
Creates a new post for the specified project. The author is taken from the authenticated user.
141+
142+
* **Method:** `POST`
143+
* **URL:** `/api/v1/projects/{projectId}/posts`
144+
* **Auth:** required
145+
146+
**Path parameters**
147+
148+
* `projectId` (`Guid`) – id of the project.
149+
150+
**Request body**
151+
152+
```json
153+
{
154+
"title": "Short title of the update",
155+
"content": "Longer description of what changed, what was shipped, etc."
156+
}
157+
```
158+
159+
Validation rules (enforced in domain or service):
160+
161+
* `title` – required, non-empty, reasonable max length.
162+
* `content` – required, non-empty.
163+
164+
**Responses**
165+
166+
* `201 Created` – post created successfully.
167+
168+
Response body: `PostResponse` for the created post.
169+
170+
* `400 Bad Request` – invalid input (for example missing or empty `title`/`content`).
171+
* `404 Not Found` – project does not exist or is not visible to the user.
172+
173+
---
174+
175+
### 3. Get a single post
176+
177+
Returns a single post by id within the context of a project.
178+
179+
* **Method:** `GET`
180+
* **URL:** `/api/v1/projects/{projectId}/posts/{postId}`
181+
* **Auth:** required
182+
183+
**Path parameters**
184+
185+
* `projectId` (`Guid`) – id of the project.
186+
* `postId` (`Guid`) – id of the post.
187+
188+
**Responses**
189+
190+
* `200 OK` – returns `PostResponse`.
191+
* `404 Not Found` – if the post does not exist, does not belong to the given project, or is not visible to the current user.
192+
193+
---
194+
195+
### 4. Update a post
196+
197+
Updates the title and content of an existing post.
198+
199+
* **Method:** `PUT`
200+
* **URL:** `/api/v1/projects/{projectId}/posts/{postId}`
201+
* **Auth:** required
202+
203+
Only the author (and in the future possibly project owners/admins) can update a post.
204+
205+
**Path parameters**
206+
207+
* `projectId` (`Guid`)
208+
* `postId` (`Guid`)
209+
210+
**Request body**
211+
212+
Same shape as create:
213+
214+
```json
215+
{
216+
"title": "Updated title",
217+
"content": "Updated content of the post"
218+
}
219+
```
220+
221+
**Responses**
222+
223+
* `200 OK` (or `204 No Content`, depending on implementation) – post updated.
224+
225+
* If `200`, response body should be the updated `PostResponse`.
226+
* `400 Bad Request` – invalid input.
227+
* `403 Forbidden` – current user is not allowed to edit this post.
228+
* `404 Not Found` – post not found or does not belong to the given project.
229+
230+
---
231+
232+
### 5. Delete a post
233+
234+
Deletes a post permanently.
235+
236+
* **Method:** `DELETE`
237+
* **URL:** `/api/v1/projects/{projectId}/posts/{postId}`
238+
* **Auth:** required
239+
240+
Only the author (and later possibly project admins) can delete a post.
241+
242+
**Path parameters**
243+
244+
* `projectId` (`Guid`)
245+
* `postId` (`Guid`)
246+
247+
**Responses**
248+
249+
* `204 No Content` – post deleted successfully.
250+
* `403 Forbidden` – user is not allowed to delete this post.
251+
* `404 Not Found` – post not found or does not belong to the given project.
252+
253+
## Error format
254+
255+
The Posts API uses the same error format as the rest of Sparkly (for example a problem-details style object).
256+
257+
Typical shape:
258+
259+
```json
260+
{
261+
"type": "validation_error",
262+
"title": "Validation failed",
263+
"status": 400,
264+
"errors": {
265+
"title": ["Title is required."],
266+
"content": ["Content is required."]
267+
}
268+
}
269+
```
270+
271+
Keep any future validation or authorization errors for posts consistent with this format so the frontend can handle them uniformly.
272+
273+
## Notes for maintainers
274+
275+
* When adding new fields to `Post`, update:
276+
277+
* the EF Core mapping and migrations,
278+
* the `PostResponse` DTO and mapper,
279+
* this document and any frontend models.
280+
* When changing routes or authorization rules, keep the behaviour described above in sync with the actual implementation.
281+
* Consider adding Swagger / OpenAPI annotations to `PostsController` so these endpoints also appear in generated API documentation.

0 commit comments

Comments
 (0)