feat: 投稿取得のAPIを追加

This commit is contained in:
usbharu 2023-05-17 21:56:06 +09:00
parent 5b2d2956bd
commit 21ea966ca3
8 changed files with 178 additions and 3 deletions

View File

@ -0,0 +1,3 @@
package dev.usbharu.hideout.domain.model
data class Acct(val username: String, val domain: String? = null, val isRemote: Boolean = domain == null)

View File

@ -1,3 +1,12 @@
package dev.usbharu.hideout.domain.model.hideout.dto package dev.usbharu.hideout.domain.model.hideout.dto
data class PostCreateDto(val text: String, val userId: Long) import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
data class PostCreateDto(
val text: String,
val overview: String? = null,
val visibility: Visibility,
val repostId: Long? = null,
val repolyId: Long? = null,
val userId: Long
)

View File

@ -0,0 +1,8 @@
package dev.usbharu.hideout.exception
class PostNotFoundException : IllegalArgumentException {
constructor() : super()
constructor(s: String?) : super(s)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}

View File

@ -3,6 +3,7 @@ package dev.usbharu.hideout.plugins
import dev.usbharu.hideout.routing.activitypub.inbox import dev.usbharu.hideout.routing.activitypub.inbox
import dev.usbharu.hideout.routing.activitypub.outbox import dev.usbharu.hideout.routing.activitypub.outbox
import dev.usbharu.hideout.routing.activitypub.usersAP import dev.usbharu.hideout.routing.activitypub.usersAP
import dev.usbharu.hideout.routing.api.internal.v1.posts
import dev.usbharu.hideout.routing.api.mastodon.v1.statuses import dev.usbharu.hideout.routing.api.mastodon.v1.statuses
import dev.usbharu.hideout.routing.wellknown.webfinger import dev.usbharu.hideout.routing.wellknown.webfinger
import dev.usbharu.hideout.service.IPostService import dev.usbharu.hideout.service.IPostService
@ -31,5 +32,8 @@ fun Application.configureRouting(
route("/api/v1") { route("/api/v1") {
statuses(postService) statuses(postService)
} }
route("/api/internal/v1") {
posts(postService)
}
} }
} }

View File

@ -1,9 +1,13 @@
package dev.usbharu.hideout.routing.api.internal.v1 package dev.usbharu.hideout.routing.api.internal.v1
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto
import dev.usbharu.hideout.domain.model.hideout.form.Post import dev.usbharu.hideout.domain.model.hideout.form.Post
import dev.usbharu.hideout.exception.ParameterNotExistException
import dev.usbharu.hideout.exception.PostNotFoundException
import dev.usbharu.hideout.plugins.TOKEN_AUTH import dev.usbharu.hideout.plugins.TOKEN_AUTH
import dev.usbharu.hideout.service.IPostService import dev.usbharu.hideout.service.IPostService
import dev.usbharu.hideout.util.AcctUtil
import dev.usbharu.hideout.util.InstantParseUtil import dev.usbharu.hideout.util.InstantParseUtil
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
@ -21,7 +25,14 @@ fun Route.posts(postService: IPostService) {
val userId = principal.payload.getClaim("uid").asLong() val userId = principal.payload.getClaim("uid").asLong()
val receive = call.receive<Post>() val receive = call.receive<Post>()
val postCreateDto = PostCreateDto(receive.text, userId) val postCreateDto = PostCreateDto(
receive.text,
receive.overview,
receive.visibility,
receive.repostId,
receive.replyId,
userId
)
val create = postService.create(postCreateDto) val create = postService.create(postCreateDto)
call.response.header("Location", create.url) call.response.header("Location", create.url)
call.respond(HttpStatusCode.OK) call.respond(HttpStatusCode.OK)
@ -35,7 +46,46 @@ fun Route.posts(postService: IPostService) {
val minId = call.request.queryParameters["minId"]?.toLong() val minId = call.request.queryParameters["minId"]?.toLong()
val maxId = call.request.queryParameters["maxId"]?.toLong() val maxId = call.request.queryParameters["maxId"]?.toLong()
val limit = call.request.queryParameters["limit"]?.toInt() val limit = call.request.queryParameters["limit"]?.toInt()
postService.findAll(since, until, minId, maxId, limit, userId) call.respond(postService.findAll(since, until, minId, maxId, limit, userId))
}
get("/{id}") {
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
val id = call.parameters["id"]?.toLong()
?: throw ParameterNotExistException("Parameter(id='postsId') does not exist.")
val post = (postService.findByIdForUser(id, userId)
?: throw PostNotFoundException("$id was not found or is not authorized."))
call.respond(post)
}
}
}
route("/users/{name}/posts") {
authenticate(TOKEN_AUTH, optional = true) {
get {
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
val targetUserName = call.parameters["name"]
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
val targetUserId = targetUserName.toLongOrNull()
val posts = if (targetUserId == null) {
val acct = AcctUtil.parse(targetUserName)
postService.findByUserNameAndDomainForUser(
acct.username,
acct.domain ?: Config.configData.domain,
forUserId = userId
)
} else {
postService.findByUserIdForUser(targetUserId, forUserId = userId)
}
call.respond(posts)
}
get("/{id}") {
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
val id = call.parameters["id"]?.toLong()
?: throw ParameterNotExistException("Parameter(name='postsId' does not exist.")
val post = (postService.findByIdForUser(id, userId)
?: throw PostNotFoundException("$id was not found or is not authorized."))
call.response.header("Content-Location", post.url)
call.respond(post)
} }
} }
} }

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.service package dev.usbharu.hideout.service
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto
import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.domain.model.hideout.entity.Post
import java.time.Instant import java.time.Instant
@ -17,5 +18,43 @@ interface IPostService {
): List<Post> ): List<Post>
suspend fun findById(id: String): Post suspend fun findById(id: String): Post
/**
* 権限を考慮して投稿を取得します
*
* @param id
* @param userId
* @return
*/
suspend fun findByIdForUser(id: Long, userId: Long?): Post?
/**
* 権限を考慮してユーザーの投稿を取得します
*
* @param userId
* @param forUserId
* @return
*/
suspend fun findByUserIdForUser(
userId: Long,
since: Instant? = null,
until: Instant? = null,
minId: Long? = null,
maxId: Long? = null,
limit: Int? = null,
forUserId: Long? = null
): List<Post>
suspend fun findByUserNameAndDomainForUser(
userName: String,
domain: String = Config.configData.domain,
since: Instant? = null,
until: Instant? = null,
minId: Long? = null,
maxId: Long? = null,
limit: Int? = null,
forUserId: Long? = null
): List<Post>
suspend fun delete(id: String) suspend fun delete(id: String)
} }

View File

@ -9,6 +9,10 @@ import dev.usbharu.hideout.repository.UsersFollowers
import dev.usbharu.hideout.repository.toPost import dev.usbharu.hideout.repository.toPost
import dev.usbharu.hideout.service.IPostService import dev.usbharu.hideout.service.IPostService
import dev.usbharu.hideout.service.activitypub.ActivityPubNoteService import dev.usbharu.hideout.service.activitypub.ActivityPubNoteService
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inSubQuery
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.orIfNotNull
import org.jetbrains.exposed.sql.orWhere import org.jetbrains.exposed.sql.orWhere
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
@ -71,6 +75,23 @@ class PostService(
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun findByIdForUser(id: Long, userId: Long?): Post? {
return transaction {
val select = Posts.select(
Posts.id.eq(id).and(
Posts.visibility.eq(Visibility.PUBLIC.ordinal).orIfNotNull(
userId?.let {
Posts.userId.inSubQuery(
UsersFollowers.slice(UsersFollowers.userId).select(UsersFollowers.followerId.eq(userId))
)
}
)
)
)
select.singleOrNull()?.toPost()
}
}
override suspend fun delete(id: String) { override suspend fun delete(id: String) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@ -0,0 +1,41 @@
package dev.usbharu.hideout.util
import dev.usbharu.hideout.domain.model.Acct
object AcctUtil {
fun parse(string: String): Acct {
if (string.isBlank()) {
throw IllegalArgumentException("Invalid acct.(Blank)")
}
return when (string.count { c -> c == '@' }) {
0 -> {
Acct(string)
}
1 -> {
if (string.startsWith("@")) {
Acct(string.substring(1 until string.length))
} else {
Acct(string.substringBefore("@"), string.substringAfter("@"))
}
}
2 -> {
if (string.startsWith("@")) {
val substring = string.substring(1 until string.length)
val userName = substring.substringBefore("@")
val domain = substring.substringAfter("@")
Acct(
userName, domain.ifBlank { null }
)
} else {
throw IllegalArgumentException("Invalid acct.(@ are in the wrong position)")
}
}
else -> {
throw IllegalArgumentException("Invalid acct.(Too many @)")
}
}
}
}