Skip to content

Commit 6073343

Browse files
Added the Show page for a BlogPost, and implemented the API for it.
Co-authored-by: Enrico Polanski <enricopolanski@gmail.com>
1 parent d8fcfc2 commit 6073343

File tree

7 files changed

+106
-10
lines changed

7 files changed

+106
-10
lines changed

_project/api/_src/Usecases/Blog.Controllers.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { BlogRsc } from "@effect-app-boilerplate/resources"
44

55
const { controllers, matchWithServices } = matchFor(BlogRsc)
66

7+
const FindPost = matchWithServices("FindPost")(
8+
{ BlogPostRepo },
9+
(req, { BlogPostRepo }) =>
10+
BlogPostRepo.find(req.id)
11+
.map(_ => _.getOrNull)
12+
)
13+
714
const GetPosts = matchWithServices("GetPosts")(
815
{ BlogPostRepo },
916
(_, { BlogPostRepo }) => BlogPostRepo.all.map(items => ({ items }))
@@ -17,4 +24,4 @@ const CreatePost = matchWithServices("CreatePost")(
1724
.map(_ => _.id)
1825
)
1926

20-
export const BlogControllers = controllers(Effect.struct({ GetPosts, CreatePost }))
27+
export const BlogControllers = controllers(Effect.struct({ FindPost, GetPosts, CreatePost }))

_project/api/_src/services/BlogPostRepo.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import type { BlogPost } from "@effect-ts-app/boilerplate-types/Blog"
1+
import type { BlogPost, BlogPostId } from "@effect-app-boilerplate/models/Blog"
22

33
export interface BlogPostRepo {
44
all: Effect<never, never, readonly BlogPost[]>
5+
find: (id: BlogPostId) => Effect<never, never, Option<BlogPost>>
56
save: (post: BlogPost) => Effect<never, never, void>
67
}
78
export const BlogPostRepo = Tag<BlogPostRepo>()
@@ -11,6 +12,7 @@ export const BlogPostRepoLive = Layer(BlogPostRepo, () => {
1112

1213
return {
1314
all: Effect([...items]),
15+
find: id => Effect(items.findFirst(_ => _.id === id)),
1416
save: post => Effect(items.push(post))
1517
}
1618
})

_project/api/openapi.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,67 @@
111111
}
112112
}
113113
},
114+
"/blog/posts/{id}": {
115+
"get": {
116+
"parameters": [
117+
{
118+
"name": "id",
119+
"in": "path",
120+
"required": true,
121+
"schema": {
122+
"minLength": 6,
123+
"maxLength": 50,
124+
"title": "BlogPostId",
125+
"type": "string"
126+
}
127+
}
128+
],
129+
"responses": {
130+
"200": {
131+
"description": "OK",
132+
"content": {
133+
"application/json": {
134+
"schema": {
135+
"properties": {
136+
"id": {
137+
"minLength": 6,
138+
"maxLength": 50,
139+
"title": "BlogPostId",
140+
"type": "string"
141+
},
142+
"title": {
143+
"minLength": 1,
144+
"maxLength": 255,
145+
"type": "string"
146+
},
147+
"body": {
148+
"minLength": 1,
149+
"maxLength": 2047,
150+
"type": "string"
151+
},
152+
"createdAt": {
153+
"format": "date-time",
154+
"type": "string"
155+
}
156+
},
157+
"required": [
158+
"id",
159+
"title",
160+
"body",
161+
"createdAt"
162+
],
163+
"type": "object",
164+
"nullable": true
165+
}
166+
}
167+
}
168+
},
169+
"400": {
170+
"description": "ValidationError"
171+
}
172+
}
173+
}
174+
},
114175
"/hello-world": {
115176
"get": {
116177
"parameters": [],
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script setup lang="ts">
2+
import { BlogRsc } from "@effect-app-boilerplate/resources"
3+
import { BlogPostId } from "@effect-app-boilerplate/models/Blog"
4+
5+
const { id } = useRouteParams({ id: BlogPostId })
6+
7+
const blogClient = clientFor(BlogRsc)
8+
const [, latestPost] = useSafeQueryWithArg(blogClient.findPost, { id })
9+
</script>
10+
11+
<template>
12+
<div v-if="latestPost">
13+
<div>Title: {{ latestPost.title }}</div>
14+
<div>Body: {{ latestPost.body }}</div>
15+
</div>
16+
</template>

_project/frontend-nuxt/pages/blog.vue renamed to _project/frontend-nuxt/pages/blog/index.vue

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,6 @@ const createPost = flow(createPost_, _ => _.then(_ => reloadPosts()))
1111

1212
<template>
1313
<div>
14-
Here's a Post List
15-
16-
<ul v-if="latestPosts">
17-
<li v-for="post in latestPosts.items" :key="post.id">
18-
{{ post.title }}
19-
</li>
20-
</ul>
21-
2214
<div>
2315
a new Title and a new body
2416
<button
@@ -32,5 +24,13 @@ const createPost = flow(createPost_, _ => _.then(_ => reloadPosts()))
3224
Create new post
3325
</button>
3426
</div>
27+
Here's a Post List
28+
<ul v-if="latestPosts">
29+
<li v-for="post in latestPosts.items" :key="post.id">
30+
<nuxt-link :to="{ name: 'blog-id', params: { id: post.id } }">{{
31+
post.title
32+
}}</nuxt-link>
33+
</li>
34+
</ul>
3535
</div>
3636
</template>

_project/resources/_src/Blog.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// codegen:start {preset: barrel, include: ./Blog/*.ts, export: { as: 'PascalCase' }, nodir: false }
22
export * as CreatePost from "./Blog/CreatePost.js"
3+
export * as FindPost from "./Blog/FindPost.js"
34
export * as GetPosts from "./Blog/GetPosts.js"
45
// codegen:end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { BlogPost, BlogPostId } from "@effect-app-boilerplate/models/Blog"
2+
3+
@allowAnonymous
4+
@allowRoles("user")
5+
export class FindPostRequest extends Get("/blog/posts/:id")<FindPostRequest>()({
6+
id: prop(BlogPostId)
7+
}) {}
8+
9+
export const FindPostResponse = nullable(BlogPost)

0 commit comments

Comments
 (0)