Skip to content

Commit 68c1130

Browse files
committed
Merge branch 'main' into beta5
2 parents f2dc4d3 + 920b609 commit 68c1130

File tree

8 files changed

+288
-16
lines changed

8 files changed

+288
-16
lines changed

src/main/kotlin/io/github/nomisrev/env/Dependencies.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ suspend fun ResourceScope.dependencies(env: Env): Dependencies {
3131
val hikari = hikari(env.dataSource)
3232
val sqlDelight = sqlDelight(hikari)
3333
val userRepo = userPersistence(sqlDelight.usersQueries, sqlDelight.followingQueries)
34-
val articleRepo = articleRepo(sqlDelight.articlesQueries, sqlDelight.tagsQueries)
34+
val articleRepo =
35+
articleRepo(sqlDelight.articlesQueries, sqlDelight.commentsQueries, sqlDelight.tagsQueries)
3536
val tagPersistence = tagPersistence(sqlDelight.tagsQueries)
3637
val favouritePersistence = favouritePersistence(sqlDelight.favoritesQueries)
3738
val jwtService = jwtService(env.auth, userRepo)

src/main/kotlin/io/github/nomisrev/repo/ArticlePersistence.kt

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import io.github.nomisrev.routes.Profile
1111
import io.github.nomisrev.service.Slug
1212
import io.github.nomisrev.sqldelight.Articles
1313
import io.github.nomisrev.sqldelight.ArticlesQueries
14+
import io.github.nomisrev.sqldelight.Comments
15+
import io.github.nomisrev.sqldelight.CommentsQueries
1416
import io.github.nomisrev.sqldelight.TagsQueries
1517
import java.time.OffsetDateTime
1618

@@ -37,9 +39,19 @@ interface ArticlePersistence {
3739
suspend fun getFeed(userId: UserId, limit: FeedLimit, offset: FeedOffset): List<Article>
3840

3941
suspend fun getArticleBySlug(slug: Slug): Either<ArticleBySlugNotFound, Articles>
42+
43+
suspend fun insertCommentForArticleSlug(
44+
slug: Slug,
45+
userId: UserId,
46+
comment: String,
47+
articleId: ArticleId,
48+
createdAt: OffsetDateTime,
49+
): Comments
50+
51+
suspend fun getCommentsForSlug(slug: Slug): List<SelectForSlug>
4052
}
4153

42-
fun articleRepo(articles: ArticlesQueries, tagsQueries: TagsQueries) =
54+
fun articleRepo(articles: ArticlesQueries, comments: CommentsQueries, tagsQueries: TagsQueries) =
4355
object : ArticlePersistence {
4456
override suspend fun create(
4557
authorId: UserId,
@@ -108,4 +120,35 @@ fun articleRepo(articles: ArticlesQueries, tagsQueries: TagsQueries) =
108120
val article = articles.selectBySlug(slug.value).executeAsOneOrNull()
109121
ensureNotNull(article) { ArticleBySlugNotFound(slug.value) }
110122
}
123+
124+
override suspend fun getCommentsForSlug(slug: Slug): List<SelectForSlug> =
125+
comments.selectForSlug(slug.value).executeAsList()
126+
127+
override suspend fun insertCommentForArticleSlug(
128+
slug: Slug,
129+
userId: UserId,
130+
comment: String,
131+
articleId: ArticleId,
132+
createdAt: OffsetDateTime,
133+
) =
134+
comments.transactionWithResult {
135+
comments
136+
.insertAndGetComment(
137+
article_id = articleId.serial,
138+
body = comment,
139+
author = userId.serial,
140+
createdAt = createdAt,
141+
updatedAt = createdAt
142+
) { id, article_id, body, author, createdAt, updatedAt ->
143+
Comments(
144+
id = id,
145+
body = body,
146+
author = author,
147+
createdAt = createdAt,
148+
updatedAt = updatedAt,
149+
article_id = article_id
150+
)
151+
}
152+
.executeAsOne()
153+
}
111154
}

src/main/kotlin/io/github/nomisrev/routes/articles.kt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ package io.github.nomisrev.routes
22

33
import arrow.core.raise.either
44
import io.github.nomisrev.auth.jwtAuth
5+
import io.github.nomisrev.repo.UserId
56
import io.github.nomisrev.service.ArticleService
67
import io.github.nomisrev.service.CreateArticle
78
import io.github.nomisrev.service.JwtService
89
import io.github.nomisrev.service.Slug
10+
import io.github.nomisrev.service.UserService
911
import io.github.nomisrev.validate
1012
import io.ktor.http.HttpStatusCode
1113
import io.ktor.resources.Resource
1214
import io.ktor.server.request.receive
1315
import io.ktor.server.resources.get
1416
import io.ktor.server.resources.post
17+
import io.ktor.server.response.respond
1518
import io.ktor.server.routing.Route
1619
import java.time.OffsetDateTime
1720
import kotlinx.serialization.KSerializer
@@ -51,6 +54,10 @@ data class MultipleArticlesResponse(
5154

5255
@JvmInline @Serializable value class FeedLimit(val limit: Int)
5356

57+
@Serializable data class NewComment(val body: String)
58+
59+
@Serializable data class SingleCommentResponse(val comment: Comment)
60+
5461
@Serializable
5562
data class Comment(
5663
val commentId: Long,
@@ -60,6 +67,8 @@ data class Comment(
6067
val author: Profile
6168
)
6269

70+
@Serializable data class MultipleCommentsResponse(val comments: List<Comment>)
71+
6372
@Serializable
6473
data class NewArticle(
6574
val title: String,
@@ -96,6 +105,9 @@ data class ArticleResource(val parent: RootResource = RootResource) {
96105
data class ArticlesResource(val parent: RootResource = RootResource) {
97106
@Resource("{slug}")
98107
data class Slug(val parent: ArticlesResource = ArticlesResource(), val slug: String)
108+
109+
@Resource("{slug}/comments")
110+
data class Comments(val parent: ArticlesResource = ArticlesResource(), val slug: String)
99111
}
100112

101113
fun Route.articleRoutes(
@@ -157,6 +169,53 @@ fun Route.articleRoutes(
157169
}
158170
}
159171

172+
fun Route.commentRoutes(
173+
userService: UserService,
174+
articleService: ArticleService,
175+
jwtService: JwtService
176+
) {
177+
post<ArticlesResource.Comments> { slug ->
178+
jwtAuth(jwtService) { (_, userId) ->
179+
either {
180+
val comments =
181+
articleService
182+
.insertCommentForArticleSlug(
183+
slug = Slug(slug.slug),
184+
userId = userId,
185+
comment = call.receive<NewComment>().validate().bind().body,
186+
)
187+
.bind()
188+
val userProfile = userService.getUser(UserId(comments.author)).bind()
189+
SingleCommentResponse(
190+
Comment(
191+
commentId = comments.id,
192+
createdAt = comments.createdAt,
193+
updatedAt = comments.updatedAt,
194+
body = comments.body,
195+
author =
196+
Profile(
197+
username = userProfile.username,
198+
bio = userProfile.bio,
199+
image = userProfile.image,
200+
following = false
201+
)
202+
)
203+
)
204+
}
205+
.respond(HttpStatusCode.OK)
206+
}
207+
}
208+
}
209+
210+
fun Route.commentRoutes(articleService: ArticleService, jwtService: JwtService) {
211+
get<ArticlesResource.Comments> { slug ->
212+
jwtAuth(jwtService) { (_, _) ->
213+
val comments = articleService.getCommentsForSlug(Slug(slug.slug))
214+
call.respond(MultipleCommentsResponse(comments))
215+
}
216+
}
217+
}
218+
160219
private object OffsetDateTimeIso8601Serializer : KSerializer<OffsetDateTime> {
161220
override val descriptor: SerialDescriptor =
162221
PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.STRING)

src/main/kotlin/io/github/nomisrev/routes/root.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ fun Application.routes(deps: Dependencies) = routing {
99
userRoutes(deps.userService, deps.jwtService)
1010
tagRoutes(deps.tagPersistence)
1111
articleRoutes(deps.articleService, deps.jwtService)
12+
commentRoutes(deps.userService, deps.articleService, deps.jwtService)
1213
profileRoutes(deps.userPersistence, deps.jwtService)
14+
commentRoutes(deps.articleService, deps.jwtService)
1315
}
1416

1517
@Resource("/api") data object RootResource

src/main/kotlin/io/github/nomisrev/service/ArticleService.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ package io.github.nomisrev.service
33
import arrow.core.Either
44
import arrow.core.raise.either
55
import io.github.nomisrev.DomainError
6+
import io.github.nomisrev.repo.ArticleId
67
import io.github.nomisrev.repo.ArticlePersistence
78
import io.github.nomisrev.repo.FavouritePersistence
89
import io.github.nomisrev.repo.TagPersistence
910
import io.github.nomisrev.repo.UserId
1011
import io.github.nomisrev.repo.UserPersistence
1112
import io.github.nomisrev.routes.Article
13+
import io.github.nomisrev.routes.Comment
1214
import io.github.nomisrev.routes.FeedLimit
1315
import io.github.nomisrev.routes.FeedOffset
1416
import io.github.nomisrev.routes.MultipleArticlesResponse
1517
import io.github.nomisrev.routes.Profile
18+
import io.github.nomisrev.sqldelight.Comments
1619
import java.time.OffsetDateTime
1720

1821
data class CreateArticle(
@@ -38,6 +41,14 @@ interface ArticleService {
3841

3942
/** Get article by Slug */
4043
suspend fun getArticleBySlug(slug: Slug): Either<DomainError, Article>
44+
45+
suspend fun insertCommentForArticleSlug(
46+
slug: Slug,
47+
userId: UserId,
48+
comment: String
49+
): Either<DomainError, Comments>
50+
51+
suspend fun getCommentsForSlug(slug: Slug): List<Comment>
4152
}
4253

4354
fun articleService(
@@ -117,4 +128,27 @@ fun articleService(
117128
articleTags
118129
)
119130
}
131+
132+
override suspend fun insertCommentForArticleSlug(slug: Slug, userId: UserId, comment: String) =
133+
either {
134+
val article = getArticleBySlug(slug).bind()
135+
articlePersistence.insertCommentForArticleSlug(
136+
slug,
137+
userId,
138+
comment,
139+
ArticleId(article.articleId),
140+
OffsetDateTime.now()
141+
)
142+
}
143+
144+
override suspend fun getCommentsForSlug(slug: Slug): List<Comment> =
145+
articlePersistence.getCommentsForSlug(slug).map { comment ->
146+
Comment(
147+
comment.comment__id,
148+
comment.comment__createdAt,
149+
comment.comment__updatedAt,
150+
comment.comment__body,
151+
Profile(comment.author__username, comment.author__bio, comment.author__image, false)
152+
)
153+
}
120154
}

src/main/kotlin/io/github/nomisrev/validation.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.github.nomisrev.routes.ArticleResource
1515
import io.github.nomisrev.routes.FeedLimit
1616
import io.github.nomisrev.routes.FeedOffset
1717
import io.github.nomisrev.routes.NewArticle
18+
import io.github.nomisrev.routes.NewComment
1819
import io.github.nomisrev.service.GetFeed
1920
import io.github.nomisrev.service.Login
2021
import io.github.nomisrev.service.RegisterUser
@@ -163,6 +164,9 @@ fun NewArticle.validate(): Either<IncorrectInput, NewArticle> =
163164
)
164165
.mapLeft(::IncorrectInput)
165166

167+
fun NewComment.validate(): Either<IncorrectInput, NewComment> =
168+
body.validBody().map { NewComment(it) }.mapLeft(::IncorrectInput)
169+
166170
const val MIN_FEED_LIMIT = 1
167171
const val MIN_FEED_OFFSET = 0
168172

src/main/sqldelight/io/github/nomisrev/sqldelight/Comments.sq

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ CREATE TABLE IF NOT EXISTS comments(
99
updatedAt VARCHAR(50) AS OffsetDateTime NOT NULL
1010
);
1111

12-
insert:
12+
insertAndGetComment:
1313
INSERT INTO comments(article_id, body, author, createdAt, updatedAt)
14-
VALUES (:article_id, :body, :author, :createdAt, :updatedAt);
14+
VALUES (:article_id, :body, :author, :createdAt, :updatedAt)
15+
RETURNING id, article_id, body, author, createdAt, updatedAt;
1516

1617
selectByArticleId:
1718
SELECT id, article_id, body, author, createdAt, updatedAt
@@ -21,3 +22,11 @@ WHERE article_id = :articleId;
2122
delete:
2223
DELETE FROM comments
2324
WHERE id = :id;
25+
26+
selectForSlug:
27+
SELECT comments.id AS comment__id, comments.article_id AS comment__articleId, comments.body AS comment__body, comments.author AS comment__author, comments.createdAt AS comment__createdAt, comments.updatedAt AS comment__updatedAt,
28+
users.username AS author__username, users.bio AS author__bio, users.image AS author__image
29+
FROM comments
30+
INNER JOIN articles ON comments.article_id = articles.id
31+
INNER JOIN users ON comments.author = users.id
32+
WHERE articles.slug = :slug;

0 commit comments

Comments
 (0)