mirror of https://github.com/usbharu/Hideout.git
commit
34e48cb429
11
detekt.yml
11
detekt.yml
|
@ -1,5 +1,7 @@
|
||||||
build:
|
build:
|
||||||
maxIssues: 20
|
maxIssues: 20
|
||||||
|
weights:
|
||||||
|
Indentation: 0
|
||||||
|
|
||||||
style:
|
style:
|
||||||
ClassOrdering:
|
ClassOrdering:
|
||||||
|
@ -32,6 +34,15 @@ style:
|
||||||
ForbiddenComment:
|
ForbiddenComment:
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
|
ThrowsCount:
|
||||||
|
active: false
|
||||||
|
|
||||||
|
UseCheckOrError:
|
||||||
|
active: false
|
||||||
|
|
||||||
|
UseRequire:
|
||||||
|
active: false
|
||||||
|
|
||||||
complexity:
|
complexity:
|
||||||
CognitiveComplexMethod:
|
CognitiveComplexMethod:
|
||||||
active: true
|
active: true
|
||||||
|
|
|
@ -100,11 +100,12 @@ fun Application.parent() {
|
||||||
inject<JwkProvider>().value,
|
inject<JwkProvider>().value,
|
||||||
)
|
)
|
||||||
configureRouting(
|
configureRouting(
|
||||||
inject<HttpSignatureVerifyService>().value,
|
httpSignatureVerifyService = inject<HttpSignatureVerifyService>().value,
|
||||||
inject<ActivityPubService>().value,
|
activityPubService = inject<ActivityPubService>().value,
|
||||||
inject<IUserService>().value,
|
userService = inject<IUserService>().value,
|
||||||
inject<ActivityPubUserService>().value,
|
activityPubUserService = inject<ActivityPubUserService>().value,
|
||||||
inject<IPostService>().value
|
postService = inject<IPostService>().value,
|
||||||
|
userApiService = inject<IUserApiService>().value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -1,54 +0,0 @@
|
||||||
package dev.usbharu.hideout.domain.model
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.repository.Users
|
|
||||||
import org.jetbrains.exposed.sql.ResultRow
|
|
||||||
import org.jetbrains.exposed.sql.Table
|
|
||||||
|
|
||||||
object Posts : Table() {
|
|
||||||
val id = long("id")
|
|
||||||
val userId = long("userId").references(Users.id)
|
|
||||||
val overview = varchar("overview", 100).nullable()
|
|
||||||
val text = varchar("text", 3000)
|
|
||||||
val createdAt = long("createdAt")
|
|
||||||
val visibility = integer("visibility").default(0)
|
|
||||||
val url = varchar("url", 500)
|
|
||||||
val repostId = long("repostId").references(id).nullable()
|
|
||||||
val replyId = long("replyId").references(id).nullable()
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Post(
|
|
||||||
val userId: Long,
|
|
||||||
val overview: String? = null,
|
|
||||||
val text: String,
|
|
||||||
val createdAt: Long,
|
|
||||||
val visibility: Int,
|
|
||||||
val repostId: Long? = null,
|
|
||||||
val replyId: Long? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
data class PostEntity(
|
|
||||||
val id: Long,
|
|
||||||
val userId: Long,
|
|
||||||
val overview: String? = null,
|
|
||||||
val text: String,
|
|
||||||
val createdAt: Long,
|
|
||||||
val visibility: Int,
|
|
||||||
val url: String,
|
|
||||||
val repostId: Long? = null,
|
|
||||||
val replyId: Long? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
fun ResultRow.toPost(): PostEntity {
|
|
||||||
return PostEntity(
|
|
||||||
id = this[Posts.id],
|
|
||||||
userId = this[Posts.userId],
|
|
||||||
overview = this[Posts.overview],
|
|
||||||
text = this[Posts.text],
|
|
||||||
createdAt = this[Posts.createdAt],
|
|
||||||
visibility = this[Posts.visibility],
|
|
||||||
url = this[Posts.url],
|
|
||||||
repostId = this[Posts.repostId],
|
|
||||||
replyId = this[Posts.replyId]
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -9,7 +9,11 @@ open class Follow : Object {
|
||||||
name: String,
|
name: String,
|
||||||
`object`: String?,
|
`object`: String?,
|
||||||
actor: String?
|
actor: String?
|
||||||
) : super(add(type, "Follow"), name, actor) {
|
) : super(
|
||||||
|
type = add(type, "Follow"),
|
||||||
|
name = name,
|
||||||
|
actor = actor
|
||||||
|
) {
|
||||||
this.`object` = `object`
|
this.`object` = `object`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,14 @@ open class Key : Object {
|
||||||
constructor(
|
constructor(
|
||||||
type: List<String>,
|
type: List<String>,
|
||||||
name: String,
|
name: String,
|
||||||
id: String?,
|
id: String,
|
||||||
owner: String?,
|
owner: String?,
|
||||||
publicKeyPem: String?
|
publicKeyPem: String?
|
||||||
) : super(add(type, "Key"), name, id) {
|
) : super(
|
||||||
|
type = add(list = type, type = "Key"),
|
||||||
|
name = name,
|
||||||
|
id = id
|
||||||
|
) {
|
||||||
this.owner = owner
|
this.owner = owner
|
||||||
this.publicKeyPem = publicKeyPem
|
this.publicKeyPem = publicKeyPem
|
||||||
}
|
}
|
||||||
|
@ -21,16 +25,16 @@ open class Key : Object {
|
||||||
if (other !is Key) return false
|
if (other !is Key) return false
|
||||||
if (!super.equals(other)) return false
|
if (!super.equals(other)) return false
|
||||||
|
|
||||||
if (id != other.id) return false
|
|
||||||
if (owner != other.owner) return false
|
if (owner != other.owner) return false
|
||||||
return publicKeyPem == other.publicKeyPem
|
return publicKeyPem == other.publicKeyPem
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = super.hashCode()
|
var result = super.hashCode()
|
||||||
result = 31 * result + (id?.hashCode() ?: 0)
|
|
||||||
result = 31 * result + (owner?.hashCode() ?: 0)
|
result = 31 * result + (owner?.hashCode() ?: 0)
|
||||||
result = 31 * result + (publicKeyPem?.hashCode() ?: 0)
|
result = 31 * result + (publicKeyPem?.hashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "Key(owner=$owner, publicKeyPem=$publicKeyPem) ${super.toString()}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ open class Object : JsonLd {
|
||||||
|
|
||||||
if (type != other.type) return false
|
if (type != other.type) return false
|
||||||
if (name != other.name) return false
|
if (name != other.name) return false
|
||||||
return actor == other.actor
|
if (actor != other.actor) return false
|
||||||
|
return id == other.id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
|
@ -35,10 +36,11 @@ open class Object : JsonLd {
|
||||||
result = 31 * result + type.hashCode()
|
result = 31 * result + type.hashCode()
|
||||||
result = 31 * result + (name?.hashCode() ?: 0)
|
result = 31 * result + (name?.hashCode() ?: 0)
|
||||||
result = 31 * result + (actor?.hashCode() ?: 0)
|
result = 31 * result + (actor?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (id?.hashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "Object(type=$type, name=$name, actor=$actor) ${super.toString()}"
|
override fun toString(): String = "Object(type=$type, name=$name, actor=$actor, id=$id) ${super.toString()}"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package dev.usbharu.hideout.domain.model.api
|
package dev.usbharu.hideout.domain.model.api.mastodon
|
||||||
|
|
||||||
data class StatusForPost(
|
data class StatusForPost(
|
||||||
val status: String,
|
val status: String,
|
|
@ -0,0 +1,12 @@
|
||||||
|
package dev.usbharu.hideout.domain.model.hideout.dto
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
|
|
||||||
|
data class PostCreateDto(
|
||||||
|
val text: String,
|
||||||
|
val overview: String? = null,
|
||||||
|
val visibility: Visibility = Visibility.PUBLIC,
|
||||||
|
val repostId: Long? = null,
|
||||||
|
val repolyId: Long? = null,
|
||||||
|
val userId: Long
|
||||||
|
)
|
|
@ -0,0 +1,27 @@
|
||||||
|
package dev.usbharu.hideout.domain.model.hideout.dto
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
|
|
||||||
|
data class UserResponse(
|
||||||
|
val id: Long,
|
||||||
|
val name: String,
|
||||||
|
val domain: String,
|
||||||
|
val screenName: String,
|
||||||
|
val description: String = "",
|
||||||
|
val url: String,
|
||||||
|
val createdAt: Long
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(user: User): UserResponse {
|
||||||
|
return UserResponse(
|
||||||
|
id = user.id,
|
||||||
|
name = user.name,
|
||||||
|
domain = user.domain,
|
||||||
|
screenName = user.screenName,
|
||||||
|
description = user.description,
|
||||||
|
url = user.url,
|
||||||
|
createdAt = user.createdAt.toEpochMilli()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package dev.usbharu.hideout.domain.model.hideout.entity
|
||||||
|
|
||||||
|
data class Post(
|
||||||
|
val id: Long,
|
||||||
|
val userId: Long,
|
||||||
|
val overview: String? = null,
|
||||||
|
val text: String,
|
||||||
|
val createdAt: Long,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val url: String,
|
||||||
|
val repostId: Long? = null,
|
||||||
|
val replyId: Long? = null
|
||||||
|
)
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.usbharu.hideout.domain.model.hideout.entity
|
||||||
|
|
||||||
|
enum class Visibility {
|
||||||
|
PUBLIC,
|
||||||
|
UNLISTED,
|
||||||
|
FOLLOWERS,
|
||||||
|
DIRECT
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package dev.usbharu.hideout.domain.model.hideout.form
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
|
|
||||||
|
data class Post(
|
||||||
|
val text: String,
|
||||||
|
val overview: String? = null,
|
||||||
|
val visibility: Visibility = Visibility.PUBLIC,
|
||||||
|
val repostId: Long? = null,
|
||||||
|
val replyId: Long? = null
|
||||||
|
)
|
|
@ -0,0 +1,3 @@
|
||||||
|
package dev.usbharu.hideout.domain.model.hideout.form
|
||||||
|
|
||||||
|
data class UserCreate(val username: String, val password: String)
|
|
@ -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)
|
||||||
|
}
|
|
@ -3,9 +3,12 @@ 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.v1.statuses
|
import dev.usbharu.hideout.routing.api.internal.v1.posts
|
||||||
|
import dev.usbharu.hideout.routing.api.internal.v1.users
|
||||||
|
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
|
||||||
|
import dev.usbharu.hideout.service.IUserApiService
|
||||||
import dev.usbharu.hideout.service.activitypub.ActivityPubService
|
import dev.usbharu.hideout.service.activitypub.ActivityPubService
|
||||||
import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
|
import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
|
||||||
import dev.usbharu.hideout.service.impl.IUserService
|
import dev.usbharu.hideout.service.impl.IUserService
|
||||||
|
@ -14,12 +17,14 @@ import io.ktor.server.application.*
|
||||||
import io.ktor.server.plugins.autohead.*
|
import io.ktor.server.plugins.autohead.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
fun Application.configureRouting(
|
fun Application.configureRouting(
|
||||||
httpSignatureVerifyService: HttpSignatureVerifyService,
|
httpSignatureVerifyService: HttpSignatureVerifyService,
|
||||||
activityPubService: ActivityPubService,
|
activityPubService: ActivityPubService,
|
||||||
userService: IUserService,
|
userService: IUserService,
|
||||||
activityPubUserService: ActivityPubUserService,
|
activityPubUserService: ActivityPubUserService,
|
||||||
postService: IPostService
|
postService: IPostService,
|
||||||
|
userApiService: IUserApiService
|
||||||
) {
|
) {
|
||||||
install(AutoHeadResponse)
|
install(AutoHeadResponse)
|
||||||
routing {
|
routing {
|
||||||
|
@ -31,5 +36,9 @@ fun Application.configureRouting(
|
||||||
route("/api/v1") {
|
route("/api/v1") {
|
||||||
statuses(postService)
|
statuses(postService)
|
||||||
}
|
}
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
users(userService, userApiService)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,11 +35,14 @@ fun Application.configureSecurity(
|
||||||
acceptLeeway(3)
|
acceptLeeway(3)
|
||||||
}
|
}
|
||||||
validate { jwtCredential ->
|
validate { jwtCredential ->
|
||||||
if (jwtCredential.payload.getClaim("username")?.asString().isNullOrBlank().not()) {
|
val uid = jwtCredential.payload.getClaim("uid")
|
||||||
JWTPrincipal(jwtCredential.payload)
|
if (uid.isMissing) {
|
||||||
} else {
|
return@validate null
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
if (uid.asLong() == null) {
|
||||||
|
return@validate null
|
||||||
|
}
|
||||||
|
return@validate JWTPrincipal(jwtCredential.payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,8 +76,8 @@ fun Application.configureSecurity(
|
||||||
}
|
}
|
||||||
authenticate(TOKEN_AUTH) {
|
authenticate(TOKEN_AUTH) {
|
||||||
get("/auth-check") {
|
get("/auth-check") {
|
||||||
val principal = call.principal<JWTPrincipal>()
|
val principal = call.principal<JWTPrincipal>() ?: throw IllegalStateException("no principal")
|
||||||
val username = principal!!.payload.getClaim("username")
|
val username = principal.payload.getClaim("uid")
|
||||||
call.respondText("Hello $username")
|
call.respondText("Hello $username")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package dev.usbharu.hideout.repository
|
package dev.usbharu.hideout.repository
|
||||||
|
|
||||||
import dev.usbharu.hideout.domain.model.Post
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
import dev.usbharu.hideout.domain.model.PostEntity
|
|
||||||
|
|
||||||
interface IPostRepository {
|
interface IPostRepository {
|
||||||
suspend fun insert(post: Post): PostEntity
|
suspend fun generateId(): Long
|
||||||
suspend fun findOneById(id: Long): PostEntity
|
suspend fun save(post: Post): Post
|
||||||
|
suspend fun findOneById(id: Long): Post
|
||||||
suspend fun delete(id: Long)
|
suspend fun delete(id: Long)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
package dev.usbharu.hideout.repository
|
package dev.usbharu.hideout.repository
|
||||||
|
|
||||||
import dev.usbharu.hideout.config.Config
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
import dev.usbharu.hideout.domain.model.Post
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
import dev.usbharu.hideout.domain.model.PostEntity
|
|
||||||
import dev.usbharu.hideout.domain.model.Posts
|
|
||||||
import dev.usbharu.hideout.domain.model.toPost
|
|
||||||
import dev.usbharu.hideout.service.IdGenerateService
|
import dev.usbharu.hideout.service.IdGenerateService
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
|
@ -22,41 +19,30 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun generateId(): Long = idGenerateService.generateId()
|
||||||
|
|
||||||
@Suppress("InjectDispatcher")
|
@Suppress("InjectDispatcher")
|
||||||
suspend fun <T> query(block: suspend () -> T): T =
|
suspend fun <T> query(block: suspend () -> T): T =
|
||||||
newSuspendedTransaction(Dispatchers.IO) { block() }
|
newSuspendedTransaction(Dispatchers.IO) { block() }
|
||||||
|
|
||||||
override suspend fun insert(post: Post): PostEntity {
|
override suspend fun save(post: Post): Post {
|
||||||
return query {
|
return query {
|
||||||
val generateId = idGenerateService.generateId()
|
|
||||||
val name = Users.select { Users.id eq post.userId }.single().toUser().name
|
|
||||||
val postUrl = Config.configData.url + "/users/$name/posts/$generateId"
|
|
||||||
Posts.insert {
|
Posts.insert {
|
||||||
it[id] = generateId
|
it[id] = post.id
|
||||||
it[userId] = post.userId
|
it[userId] = post.userId
|
||||||
it[overview] = post.overview
|
it[overview] = post.overview
|
||||||
it[text] = post.text
|
it[text] = post.text
|
||||||
it[createdAt] = post.createdAt
|
it[createdAt] = post.createdAt
|
||||||
it[visibility] = post.visibility
|
it[visibility] = post.visibility.ordinal
|
||||||
it[url] = postUrl
|
it[url] = post.url
|
||||||
it[repostId] = post.repostId
|
it[repostId] = post.repostId
|
||||||
it[replyId] = post.replyId
|
it[replyId] = post.replyId
|
||||||
}
|
}
|
||||||
return@query PostEntity(
|
return@query post
|
||||||
id = generateId,
|
|
||||||
userId = post.userId,
|
|
||||||
overview = post.overview,
|
|
||||||
text = post.text,
|
|
||||||
createdAt = post.createdAt,
|
|
||||||
visibility = post.visibility,
|
|
||||||
url = postUrl,
|
|
||||||
repostId = post.repostId,
|
|
||||||
replyId = post.replyId
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findOneById(id: Long): PostEntity {
|
override suspend fun findOneById(id: Long): Post {
|
||||||
return query {
|
return query {
|
||||||
Posts.select { Posts.id eq id }.single().toPost()
|
Posts.select { Posts.id eq id }.single().toPost()
|
||||||
}
|
}
|
||||||
|
@ -68,3 +54,30 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Posts : Table() {
|
||||||
|
val id = long("id")
|
||||||
|
val userId = long("userId").references(Users.id)
|
||||||
|
val overview = varchar("overview", 100).nullable()
|
||||||
|
val text = varchar("text", 3000)
|
||||||
|
val createdAt = long("createdAt")
|
||||||
|
val visibility = integer("visibility").default(0)
|
||||||
|
val url = varchar("url", 500)
|
||||||
|
val repostId = long("repostId").references(id).nullable()
|
||||||
|
val replyId = long("replyId").references(id).nullable()
|
||||||
|
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ResultRow.toPost(): Post {
|
||||||
|
return Post(
|
||||||
|
id = this[Posts.id],
|
||||||
|
userId = this[Posts.userId],
|
||||||
|
overview = this[Posts.overview],
|
||||||
|
text = this[Posts.text],
|
||||||
|
createdAt = this[Posts.createdAt],
|
||||||
|
visibility = Visibility.values().first { visibility -> visibility.ordinal == this[Posts.visibility] },
|
||||||
|
url = this[Posts.url],
|
||||||
|
repostId = this[Posts.repostId],
|
||||||
|
replyId = this[Posts.replyId]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,9 @@ fun Routing.usersAP(activityPubUserService: ActivityPubUserService, userService:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
get {
|
get {
|
||||||
val userEntity = userService.findByNameLocalUser(call.parameters["name"]!!)
|
val userEntity = userService.findByNameLocalUser(
|
||||||
|
call.parameters["name"] ?: throw ParameterNotExistException("Parameter(name='name') does not exist.")
|
||||||
|
)
|
||||||
call.respondText(userEntity.toString() + "\n" + userService.findFollowersById(userEntity.id))
|
call.respondText(userEntity.toString() + "\n" + userService.findFollowersById(userEntity.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
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.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.service.IPostService
|
||||||
|
import dev.usbharu.hideout.util.AcctUtil
|
||||||
|
import dev.usbharu.hideout.util.InstantParseUtil
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
fun Route.posts(postService: IPostService) {
|
||||||
|
route("/posts") {
|
||||||
|
authenticate(TOKEN_AUTH) {
|
||||||
|
post {
|
||||||
|
val principal = call.principal<JWTPrincipal>() ?: throw IllegalStateException("no principal")
|
||||||
|
val userId = principal.payload.getClaim("uid").asLong()
|
||||||
|
|
||||||
|
val receive = call.receive<Post>()
|
||||||
|
val postCreateDto = PostCreateDto(
|
||||||
|
text = receive.text,
|
||||||
|
overview = receive.overview,
|
||||||
|
visibility = receive.visibility,
|
||||||
|
repostId = receive.repostId,
|
||||||
|
repolyId = receive.replyId,
|
||||||
|
userId = userId
|
||||||
|
)
|
||||||
|
val create = postService.create(postCreateDto)
|
||||||
|
call.response.header("Location", create.url)
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authenticate(TOKEN_AUTH, optional = true) {
|
||||||
|
get {
|
||||||
|
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
|
||||||
|
val since = InstantParseUtil.parse(call.request.queryParameters["since"])
|
||||||
|
val until = InstantParseUtil.parse(call.request.queryParameters["until"])
|
||||||
|
val minId = call.request.queryParameters["minId"]?.toLong()
|
||||||
|
val maxId = call.request.queryParameters["maxId"]?.toLong()
|
||||||
|
val limit = call.request.queryParameters["limit"]?.toInt()
|
||||||
|
call.respond(HttpStatusCode.OK, 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.respond(post)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package dev.usbharu.hideout.routing.api.internal.v1
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.config.Config
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.form.UserCreate
|
||||||
|
import dev.usbharu.hideout.exception.ParameterNotExistException
|
||||||
|
import dev.usbharu.hideout.plugins.TOKEN_AUTH
|
||||||
|
import dev.usbharu.hideout.service.IUserApiService
|
||||||
|
import dev.usbharu.hideout.service.impl.IUserService
|
||||||
|
import dev.usbharu.hideout.util.AcctUtil
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
fun Route.users(userService: IUserService, userApiService: IUserApiService) {
|
||||||
|
route("/users") {
|
||||||
|
get {
|
||||||
|
call.respond(userApiService.findAll())
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
val userCreate = call.receive<UserCreate>()
|
||||||
|
if (userService.usernameAlreadyUse(userCreate.username)) {
|
||||||
|
return@post call.respond(HttpStatusCode.BadRequest)
|
||||||
|
}
|
||||||
|
val user = userService.createLocalUser(
|
||||||
|
UserCreateDto(
|
||||||
|
userCreate.username,
|
||||||
|
userCreate.username,
|
||||||
|
"",
|
||||||
|
userCreate.password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
call.response.header("Location", "${Config.configData.url}/api/internal/v1/users/${user.name}")
|
||||||
|
call.respond(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
route("/{name}") {
|
||||||
|
authenticate(TOKEN_AUTH, optional = true) {
|
||||||
|
get {
|
||||||
|
val userParameter = (
|
||||||
|
call.parameters["name"]
|
||||||
|
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
|
||||||
|
)
|
||||||
|
if (userParameter.toLongOrNull() != null) {
|
||||||
|
return@get call.respond(userApiService.findById(userParameter.toLong()))
|
||||||
|
} else {
|
||||||
|
val acct = AcctUtil.parse(userParameter)
|
||||||
|
return@get call.respond(userApiService.findByAcct(acct))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
route("/followers") {
|
||||||
|
get {
|
||||||
|
val userParameter = call.parameters["name"]
|
||||||
|
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
|
||||||
|
if (userParameter.toLongOrNull() != null) {
|
||||||
|
return@get call.respond(userApiService.findFollowers(userParameter.toLong()))
|
||||||
|
}
|
||||||
|
val acct = AcctUtil.parse(userParameter)
|
||||||
|
return@get call.respond(userApiService.findFollowersByAcct(acct))
|
||||||
|
}
|
||||||
|
authenticate(TOKEN_AUTH) {
|
||||||
|
post {
|
||||||
|
val userId = call.principal<JWTPrincipal>()?.payload?.getClaim("uid")?.asLong()
|
||||||
|
?: throw IllegalStateException("no principal")
|
||||||
|
val userParameter = call.parameters["name"]
|
||||||
|
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
|
||||||
|
if (userParameter.toLongOrNull() != null) {
|
||||||
|
if (userService.addFollowers(userParameter.toLong(), userId)) {
|
||||||
|
return@post call.respond(HttpStatusCode.OK)
|
||||||
|
} else {
|
||||||
|
return@post call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val acct = AcctUtil.parse(userParameter)
|
||||||
|
val targetUser = userApiService.findByAcct(acct)
|
||||||
|
if (userService.addFollowers(targetUser.id, userId)) {
|
||||||
|
return@post call.respond(HttpStatusCode.OK)
|
||||||
|
} else {
|
||||||
|
return@post call.respond(HttpStatusCode.Accepted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
route("/following") {
|
||||||
|
get {
|
||||||
|
val userParameter = (
|
||||||
|
call.parameters["name"]
|
||||||
|
?: throw ParameterNotExistException("Parameter(name='userName@domain') does not exist.")
|
||||||
|
)
|
||||||
|
if (userParameter.toLongOrNull() != null) {
|
||||||
|
return@get call.respond(userApiService.findFollowings(userParameter.toLong()))
|
||||||
|
}
|
||||||
|
val acct = AcctUtil.parse(userParameter)
|
||||||
|
return@get call.respond(userApiService.findFollowingsByAcct(acct))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package dev.usbharu.hideout.routing.api.mastodon.v1
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.service.IPostService
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
@Suppress("UnusedPrivateMember")
|
||||||
|
fun Route.statuses(postService: IPostService) {
|
||||||
|
// route("/statuses") {
|
||||||
|
// post {
|
||||||
|
// val status: StatusForPost = call.receive()
|
||||||
|
// val post = dev.usbharu.hideout.domain.model.hideout.form.Post(
|
||||||
|
// userId = status.userId,
|
||||||
|
// createdAt = System.currentTimeMillis(),
|
||||||
|
// text = status.status,
|
||||||
|
// visibility = 1
|
||||||
|
// )
|
||||||
|
// postService.create(post)
|
||||||
|
// call.respond(status)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
|
@ -1,25 +0,0 @@
|
||||||
package dev.usbharu.hideout.routing.api.v1
|
|
||||||
|
|
||||||
import dev.usbharu.hideout.domain.model.Post
|
|
||||||
import dev.usbharu.hideout.domain.model.api.StatusForPost
|
|
||||||
import dev.usbharu.hideout.service.IPostService
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.request.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
|
|
||||||
fun Route.statuses(postService: IPostService) {
|
|
||||||
route("/statuses") {
|
|
||||||
post {
|
|
||||||
val status: StatusForPost = call.receive()
|
|
||||||
val post = Post(
|
|
||||||
userId = status.userId,
|
|
||||||
createdAt = System.currentTimeMillis(),
|
|
||||||
text = status.status,
|
|
||||||
visibility = 1
|
|
||||||
)
|
|
||||||
postService.create(post)
|
|
||||||
call.respond(status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,61 @@
|
||||||
package dev.usbharu.hideout.service
|
package dev.usbharu.hideout.service
|
||||||
|
|
||||||
import dev.usbharu.hideout.domain.model.Post
|
import dev.usbharu.hideout.config.Config
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
interface IPostService {
|
interface IPostService {
|
||||||
suspend fun create(post: Post)
|
suspend fun create(post: Post): Post
|
||||||
|
suspend fun create(post: PostCreateDto): Post
|
||||||
|
suspend fun findAll(
|
||||||
|
since: Instant? = null,
|
||||||
|
until: Instant? = null,
|
||||||
|
minId: Long? = null,
|
||||||
|
maxId: Long? = null,
|
||||||
|
limit: Int? = 10,
|
||||||
|
userId: Long? = null
|
||||||
|
): List<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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package dev.usbharu.hideout.service
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.Acct
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse
|
||||||
|
|
||||||
|
interface IUserApiService {
|
||||||
|
suspend fun findAll(limit: Int? = 100, offset: Long = 0): List<UserResponse>
|
||||||
|
|
||||||
|
suspend fun findById(id: Long): UserResponse
|
||||||
|
|
||||||
|
suspend fun findByIds(ids: List<Long>): List<UserResponse>
|
||||||
|
|
||||||
|
suspend fun findByAcct(acct: Acct): UserResponse
|
||||||
|
|
||||||
|
suspend fun findByAccts(accts: List<Acct>): List<UserResponse>
|
||||||
|
|
||||||
|
suspend fun findFollowers(userId: Long): List<UserResponse>
|
||||||
|
|
||||||
|
suspend fun findFollowings(userId: Long): List<UserResponse>
|
||||||
|
|
||||||
|
suspend fun findFollowersByAcct(acct: Acct): List<UserResponse>
|
||||||
|
|
||||||
|
suspend fun findFollowingsByAcct(acct: Acct): List<UserResponse>
|
||||||
|
}
|
|
@ -52,7 +52,7 @@ class JwtServiceImpl(
|
||||||
.withAudience("${Config.configData.url}/users/${user.name}")
|
.withAudience("${Config.configData.url}/users/${user.name}")
|
||||||
.withIssuer(Config.configData.url)
|
.withIssuer(Config.configData.url)
|
||||||
.withKeyId(keyId.await().toString())
|
.withKeyId(keyId.await().toString())
|
||||||
.withClaim("username", user.name)
|
.withClaim("uid", user.id)
|
||||||
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
||||||
.sign(Algorithm.RSA256(publicKey.await(), privateKey.await()))
|
.sign(Algorithm.RSA256(publicKey.await(), privateKey.await()))
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ class ServerInitialiseServiceImpl(private val metaRepository: IMetaRepository) :
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isVersionChanged(savedMeta!!)) {
|
if (isVersionChanged(requireNotNull(savedMeta))) {
|
||||||
logger.info("Version changed!! (${savedMeta.version} -> $implementationVersion)")
|
logger.info("Version changed!! (${savedMeta.version} -> $implementationVersion)")
|
||||||
updateVersion(savedMeta, implementationVersion)
|
updateVersion(savedMeta, implementationVersion)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package dev.usbharu.hideout.service
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.config.Config
|
||||||
|
import dev.usbharu.hideout.domain.model.Acct
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse
|
||||||
|
import dev.usbharu.hideout.service.impl.IUserService
|
||||||
|
import org.koin.core.annotation.Single
|
||||||
|
|
||||||
|
@Single
|
||||||
|
class UserApiServiceImpl(private val userService: IUserService) : IUserApiService {
|
||||||
|
override suspend fun findAll(limit: Int?, offset: Long): List<UserResponse> =
|
||||||
|
userService.findAll(limit, offset).map { UserResponse.from(it) }
|
||||||
|
|
||||||
|
override suspend fun findById(id: Long): UserResponse = UserResponse.from(userService.findById(id))
|
||||||
|
|
||||||
|
override suspend fun findByIds(ids: List<Long>): List<UserResponse> =
|
||||||
|
userService.findByIds(ids).map { UserResponse.from(it) }
|
||||||
|
|
||||||
|
override suspend fun findByAcct(acct: Acct): UserResponse =
|
||||||
|
UserResponse.from(userService.findByNameAndDomain(acct.username, acct.domain))
|
||||||
|
|
||||||
|
override suspend fun findByAccts(accts: List<Acct>): List<UserResponse> {
|
||||||
|
return userService.findByNameAndDomains(accts.map { it.username to (it.domain ?: Config.configData.domain) })
|
||||||
|
.map { UserResponse.from(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findFollowers(userId: Long): List<UserResponse> =
|
||||||
|
userService.findFollowersById(userId).map { UserResponse.from(it) }
|
||||||
|
|
||||||
|
override suspend fun findFollowings(userId: Long): List<UserResponse> =
|
||||||
|
userService.findFollowingById(userId).map { UserResponse.from(it) }
|
||||||
|
|
||||||
|
override suspend fun findFollowersByAcct(acct: Acct): List<UserResponse> =
|
||||||
|
userService.findFollowersByNameAndDomain(acct.username, acct.domain).map { UserResponse.from(it) }
|
||||||
|
|
||||||
|
override suspend fun findFollowingsByAcct(acct: Acct): List<UserResponse> =
|
||||||
|
userService.findFollowingByNameAndDomain(acct.username, acct.domain).map { UserResponse.from(it) }
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
package dev.usbharu.hideout.service.activitypub
|
package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
import dev.usbharu.hideout.domain.model.PostEntity
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
||||||
import kjob.core.job.JobProps
|
import kjob.core.job.JobProps
|
||||||
|
|
||||||
interface ActivityPubNoteService {
|
interface ActivityPubNoteService {
|
||||||
|
|
||||||
suspend fun createNote(post: PostEntity)
|
suspend fun createNote(post: Post)
|
||||||
suspend fun createNoteJob(props: JobProps<DeliverPostJob>)
|
suspend fun createNoteJob(props: JobProps<DeliverPostJob>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import dev.usbharu.hideout.config.Config
|
import dev.usbharu.hideout.config.Config
|
||||||
import dev.usbharu.hideout.domain.model.PostEntity
|
|
||||||
import dev.usbharu.hideout.domain.model.ap.Create
|
import dev.usbharu.hideout.domain.model.ap.Create
|
||||||
import dev.usbharu.hideout.domain.model.ap.Note
|
import dev.usbharu.hideout.domain.model.ap.Note
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
||||||
import dev.usbharu.hideout.plugins.postAp
|
import dev.usbharu.hideout.plugins.postAp
|
||||||
import dev.usbharu.hideout.service.impl.IUserService
|
import dev.usbharu.hideout.service.impl.IUserService
|
||||||
|
@ -24,7 +24,7 @@ class ActivityPubNoteServiceImpl(
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
|
||||||
override suspend fun createNote(post: PostEntity) {
|
override suspend fun createNote(post: Post) {
|
||||||
val followers = userService.findFollowersById(post.userId)
|
val followers = userService.findFollowersById(post.userId)
|
||||||
val userEntity = userService.findById(post.userId)
|
val userEntity = userService.findById(post.userId)
|
||||||
val note = Config.configData.objectMapper.writeValueAsString(post)
|
val note = Config.configData.objectMapper.writeValueAsString(post)
|
||||||
|
@ -39,7 +39,7 @@ class ActivityPubNoteServiceImpl(
|
||||||
|
|
||||||
override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) {
|
override suspend fun createNoteJob(props: JobProps<DeliverPostJob>) {
|
||||||
val actor = props[DeliverPostJob.actor]
|
val actor = props[DeliverPostJob.actor]
|
||||||
val postEntity = Config.configData.objectMapper.readValue<PostEntity>(props[DeliverPostJob.post])
|
val postEntity = Config.configData.objectMapper.readValue<Post>(props[DeliverPostJob.post])
|
||||||
val note = Note(
|
val note = Note(
|
||||||
name = "Note",
|
name = "Note",
|
||||||
id = postEntity.url,
|
id = postEntity.url,
|
||||||
|
|
|
@ -16,6 +16,8 @@ interface IUserService {
|
||||||
|
|
||||||
suspend fun findByNameLocalUser(name: String): User
|
suspend fun findByNameLocalUser(name: String): User
|
||||||
|
|
||||||
|
suspend fun findByNameAndDomain(name: String, domain: String? = null): User
|
||||||
|
|
||||||
suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User>
|
suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User>
|
||||||
|
|
||||||
suspend fun findByUrl(url: String): User
|
suspend fun findByUrl(url: String): User
|
||||||
|
@ -30,5 +32,18 @@ interface IUserService {
|
||||||
|
|
||||||
suspend fun findFollowersById(id: Long): List<User>
|
suspend fun findFollowersById(id: Long): List<User>
|
||||||
|
|
||||||
suspend fun addFollowers(id: Long, follower: Long)
|
suspend fun findFollowersByNameAndDomain(name: String, domain: String?): List<User>
|
||||||
|
|
||||||
|
suspend fun findFollowingById(id: Long): List<User>
|
||||||
|
|
||||||
|
suspend fun findFollowingByNameAndDomain(name: String, domain: String?): List<User>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* フォロワーを追加する
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @param follower
|
||||||
|
* @return リクエストが成功したか
|
||||||
|
*/
|
||||||
|
suspend fun addFollowers(id: Long, follower: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,130 @@
|
||||||
package dev.usbharu.hideout.service.impl
|
package dev.usbharu.hideout.service.impl
|
||||||
|
|
||||||
import dev.usbharu.hideout.domain.model.Post
|
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.Visibility
|
||||||
import dev.usbharu.hideout.repository.IPostRepository
|
import dev.usbharu.hideout.repository.IPostRepository
|
||||||
|
import dev.usbharu.hideout.repository.Posts
|
||||||
|
import dev.usbharu.hideout.repository.UsersFollowers
|
||||||
|
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.select
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.koin.core.annotation.Single
|
import org.koin.core.annotation.Single
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
@Single
|
@Single
|
||||||
class PostService(
|
class PostService(
|
||||||
private val postRepository: IPostRepository,
|
private val postRepository: IPostRepository,
|
||||||
private val activityPubNoteService: ActivityPubNoteService
|
private val activityPubNoteService: ActivityPubNoteService,
|
||||||
|
private val userService: IUserService
|
||||||
) : IPostService {
|
) : IPostService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(this::class.java)
|
private val logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
|
||||||
override suspend fun create(post: Post) {
|
override suspend fun create(post: Post): Post {
|
||||||
logger.debug("create post={}", post)
|
logger.debug("create post={}", post)
|
||||||
val postEntity = postRepository.insert(post)
|
val postEntity = postRepository.save(post)
|
||||||
activityPubNoteService.createNote(postEntity)
|
activityPubNoteService.createNote(postEntity)
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(post: PostCreateDto): Post {
|
||||||
|
logger.debug("create post={}", post)
|
||||||
|
val user = userService.findById(post.userId)
|
||||||
|
val id = postRepository.generateId()
|
||||||
|
val postEntity = Post(
|
||||||
|
id = id,
|
||||||
|
userId = user.id,
|
||||||
|
overview = null,
|
||||||
|
text = post.text,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
url = "${user.url}/posts/$id",
|
||||||
|
repostId = null,
|
||||||
|
replyId = null
|
||||||
|
)
|
||||||
|
postRepository.save(postEntity)
|
||||||
|
return postEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findAll(
|
||||||
|
since: Instant?,
|
||||||
|
until: Instant?,
|
||||||
|
minId: Long?,
|
||||||
|
maxId: Long?,
|
||||||
|
limit: Int?,
|
||||||
|
userId: Long?
|
||||||
|
): List<Post> {
|
||||||
|
return transaction {
|
||||||
|
val select = Posts.select {
|
||||||
|
Posts.visibility.eq(Visibility.PUBLIC.ordinal)
|
||||||
|
}
|
||||||
|
if (userId != null) {
|
||||||
|
select.orWhere {
|
||||||
|
Posts.userId.inSubQuery(
|
||||||
|
UsersFollowers.slice(UsersFollowers.userId).select(UsersFollowers.followerId eq userId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select.map { it.toPost() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findById(id: String): Post {
|
||||||
|
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 findByUserIdForUser(
|
||||||
|
userId: Long,
|
||||||
|
since: Instant?,
|
||||||
|
until: Instant?,
|
||||||
|
minId: Long?,
|
||||||
|
maxId: Long?,
|
||||||
|
limit: Int?,
|
||||||
|
forUserId: Long?
|
||||||
|
): List<Post> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByUserNameAndDomainForUser(
|
||||||
|
userName: String,
|
||||||
|
domain: String,
|
||||||
|
since: Instant?,
|
||||||
|
until: Instant?,
|
||||||
|
minId: Long?,
|
||||||
|
maxId: Long?,
|
||||||
|
limit: Int?,
|
||||||
|
forUserId: Long?
|
||||||
|
): List<Post> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(id: String) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,11 @@ class UserService(private val userRepository: IUserRepository, private val userA
|
||||||
?: throw UserNotFoundException("$name was not found.")
|
?: throw UserNotFoundException("$name was not found.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun findByNameAndDomain(name: String, domain: String?): User {
|
||||||
|
return userRepository.findByNameAndDomain(name, domain ?: Config.configData.domain)
|
||||||
|
?: throw UserNotFoundException("$name was not found.")
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> =
|
override suspend fun findByNameAndDomains(names: List<Pair<String, String>>): List<User> =
|
||||||
userRepository.findByNameAndDomains(names)
|
userRepository.findByNameAndDomains(names)
|
||||||
|
|
||||||
|
@ -87,6 +92,21 @@ class UserService(private val userRepository: IUserRepository, private val userA
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findFollowersById(id: Long): List<User> = userRepository.findFollowersById(id)
|
override suspend fun findFollowersById(id: Long): List<User> = userRepository.findFollowersById(id)
|
||||||
|
override suspend fun findFollowersByNameAndDomain(name: String, domain: String?): List<User> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun addFollowers(id: Long, follower: Long) = userRepository.createFollower(id, follower)
|
override suspend fun findFollowingById(id: Long): List<User> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findFollowingByNameAndDomain(name: String, domain: String?): List<User> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO APのフォロー処理を作る
|
||||||
|
override suspend fun addFollowers(id: Long, follower: Long): Boolean {
|
||||||
|
userRepository.createFollower(id, follower)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
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 @)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package dev.usbharu.hideout.util
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.format.DateTimeParseException
|
||||||
|
|
||||||
|
object InstantParseUtil {
|
||||||
|
fun parse(str: String?): Instant? {
|
||||||
|
return try {
|
||||||
|
Instant.ofEpochMilli(str?.toLong() ?: return null)
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
try {
|
||||||
|
Instant.parse(str)
|
||||||
|
} catch (e: DateTimeParseException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,572 @@
|
||||||
|
openapi: "3.0.3"
|
||||||
|
info:
|
||||||
|
title: "hideout API"
|
||||||
|
description: "hideout API"
|
||||||
|
version: "1.0.0"
|
||||||
|
servers:
|
||||||
|
- url: "https://hideout"
|
||||||
|
paths:
|
||||||
|
/.well-known/jwks.json:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
/auth-check:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
examples:
|
||||||
|
Example#1:
|
||||||
|
value: ""
|
||||||
|
/login:
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserLogin"
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"401":
|
||||||
|
description: "Unauthorized"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/JwtToken"
|
||||||
|
/refresh-token:
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/RefreshToken"
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/JwtToken"
|
||||||
|
/.well-known/webfinger:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "resource"
|
||||||
|
in: "query"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/WebFinger"
|
||||||
|
/api/internal/v1/posts:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "since"
|
||||||
|
in: "query"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
- name: "until"
|
||||||
|
in: "query"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
- name: "minId"
|
||||||
|
in: "query"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: "number"
|
||||||
|
- name: "maxId"
|
||||||
|
in: "query"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: "number"
|
||||||
|
- name: "limit"
|
||||||
|
in: "query"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: "integer"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Post"
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Post"
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
/api/internal/v1/posts/{id}:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "id"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "number"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Post"
|
||||||
|
/api/internal/v1/users:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/UserResponse"
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserCreate"
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"400":
|
||||||
|
description: "Bad Request"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
"201":
|
||||||
|
description: "Created"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
/api/internal/v1/users/{name}:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
/api/internal/v1/users/{name}/followers:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
type: "object"
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
"202":
|
||||||
|
description: "Accepted"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
/api/internal/v1/users/{name}/following:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
type: "object"
|
||||||
|
/api/internal/v1/users/{name}/posts:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Post"
|
||||||
|
/api/internal/v1/users/{name}/posts/{id}:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "id"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "number"
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Post"
|
||||||
|
/inbox:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
responses:
|
||||||
|
"405":
|
||||||
|
description: "Method Not Allowed"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
"501":
|
||||||
|
description: "Not Implemented"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
/outbox:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: "Not Implemented"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: "Not Implemented"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
/users/{name}:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
/users/{name}/inbox:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"405":
|
||||||
|
description: "Method Not Allowed"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
"501":
|
||||||
|
description: "Not Implemented"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
/users/{name}/outbox:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: "Not Implemented"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "name"
|
||||||
|
in: "path"
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: "Not Implemented"
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
type: "object"
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
/register:
|
||||||
|
get:
|
||||||
|
description: ""
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
post:
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: "password"
|
||||||
|
in: "query"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
- name: "username"
|
||||||
|
in: "query"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK <br> Redirect"
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
examples:
|
||||||
|
Example#1:
|
||||||
|
value: ""
|
||||||
|
Example#2:
|
||||||
|
value: "/register"
|
||||||
|
Example#3:
|
||||||
|
value: "/register"
|
||||||
|
Example#4:
|
||||||
|
value: "/register"
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
UserLogin:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: "string"
|
||||||
|
password:
|
||||||
|
type: "string"
|
||||||
|
JwtToken:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
token:
|
||||||
|
type: "string"
|
||||||
|
refreshToken:
|
||||||
|
type: "string"
|
||||||
|
RefreshToken:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
refreshToken:
|
||||||
|
type: "string"
|
||||||
|
Link:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
rel:
|
||||||
|
type: "string"
|
||||||
|
type:
|
||||||
|
type: "string"
|
||||||
|
href:
|
||||||
|
type: "string"
|
||||||
|
WebFinger:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
subject:
|
||||||
|
type: "string"
|
||||||
|
links:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Link"
|
||||||
|
Post:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
userId:
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
overview:
|
||||||
|
type: "string"
|
||||||
|
text:
|
||||||
|
type: "string"
|
||||||
|
createdAt:
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
visibility:
|
||||||
|
type: "string"
|
||||||
|
enum:
|
||||||
|
- "PUBLIC"
|
||||||
|
- "UNLISTED"
|
||||||
|
- "FOLLOWERS"
|
||||||
|
- "DIRECT"
|
||||||
|
url:
|
||||||
|
type: "string"
|
||||||
|
repostId:
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
replyId:
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
UserResponse:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
name:
|
||||||
|
type: "string"
|
||||||
|
domain:
|
||||||
|
type: "string"
|
||||||
|
screenName:
|
||||||
|
type: "string"
|
||||||
|
description:
|
||||||
|
type: "string"
|
||||||
|
url:
|
||||||
|
type: "string"
|
||||||
|
createdAt:
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
UserCreate:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: "string"
|
||||||
|
password:
|
||||||
|
type: "string"
|
|
@ -217,7 +217,7 @@ class SecurityKtTest {
|
||||||
.withAudience("${Config.configData.url}/users/test")
|
.withAudience("${Config.configData.url}/users/test")
|
||||||
.withIssuer(Config.configData.url)
|
.withIssuer(Config.configData.url)
|
||||||
.withKeyId(kid.toString())
|
.withKeyId(kid.toString())
|
||||||
.withClaim("username", "test")
|
.withClaim("uid", 123456L)
|
||||||
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
||||||
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
||||||
val metaService = mock<IMetaService> {
|
val metaService = mock<IMetaService> {
|
||||||
|
@ -255,7 +255,7 @@ class SecurityKtTest {
|
||||||
header("Authorization", "Bearer $token")
|
header("Authorization", "Bearer $token")
|
||||||
}.apply {
|
}.apply {
|
||||||
assertEquals(HttpStatusCode.OK, call.response.status)
|
assertEquals(HttpStatusCode.OK, call.response.status)
|
||||||
assertEquals("Hello \"test\"", call.response.bodyAsText())
|
assertEquals("Hello 123456", call.response.bodyAsText())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +277,7 @@ class SecurityKtTest {
|
||||||
.withAudience("${Config.configData.url}/users/test")
|
.withAudience("${Config.configData.url}/users/test")
|
||||||
.withIssuer(Config.configData.url)
|
.withIssuer(Config.configData.url)
|
||||||
.withKeyId(kid.toString())
|
.withKeyId(kid.toString())
|
||||||
.withClaim("username", "test")
|
.withClaim("uid", 123345L)
|
||||||
.withExpiresAt(now.minus(30, ChronoUnit.MINUTES))
|
.withExpiresAt(now.minus(30, ChronoUnit.MINUTES))
|
||||||
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
||||||
val metaService = mock<IMetaService> {
|
val metaService = mock<IMetaService> {
|
||||||
|
@ -335,7 +335,7 @@ class SecurityKtTest {
|
||||||
.withAudience("${Config.configData.url}/users/test")
|
.withAudience("${Config.configData.url}/users/test")
|
||||||
.withIssuer("https://example.com")
|
.withIssuer("https://example.com")
|
||||||
.withKeyId(kid.toString())
|
.withKeyId(kid.toString())
|
||||||
.withClaim("username", "test")
|
.withClaim("uid", 12345L)
|
||||||
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
||||||
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
||||||
val metaService = mock<IMetaService> {
|
val metaService = mock<IMetaService> {
|
||||||
|
@ -393,7 +393,7 @@ class SecurityKtTest {
|
||||||
.withAudience("${Config.configData.url}/users/test")
|
.withAudience("${Config.configData.url}/users/test")
|
||||||
.withIssuer(Config.configData.url)
|
.withIssuer(Config.configData.url)
|
||||||
.withKeyId(kid.toString())
|
.withKeyId(kid.toString())
|
||||||
.withClaim("username", "")
|
.withClaim("uid", null as Long?)
|
||||||
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
|
||||||
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
|
||||||
val metaService = mock<IMetaService> {
|
val metaService = mock<IMetaService> {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package dev.usbharu.hideout.routing.activitypub
|
package dev.usbharu.hideout.routing.activitypub
|
||||||
|
|
||||||
import dev.usbharu.hideout.exception.JsonParseException
|
import dev.usbharu.hideout.exception.JsonParseException
|
||||||
import dev.usbharu.hideout.plugins.configureRouting
|
|
||||||
import dev.usbharu.hideout.plugins.configureSerialization
|
import dev.usbharu.hideout.plugins.configureSerialization
|
||||||
import dev.usbharu.hideout.plugins.configureStatusPages
|
import dev.usbharu.hideout.plugins.configureStatusPages
|
||||||
import dev.usbharu.hideout.service.activitypub.ActivityPubService
|
import dev.usbharu.hideout.service.activitypub.ActivityPubService
|
||||||
|
@ -11,6 +10,7 @@ import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.config.*
|
import io.ktor.server.config.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
import io.ktor.server.testing.*
|
import io.ktor.server.testing.*
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
@ -27,7 +27,9 @@ class InboxRoutingKtTest {
|
||||||
}
|
}
|
||||||
application {
|
application {
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
configureRouting(mock(), mock(), mock(), mock(), mock())
|
routing {
|
||||||
|
inbox(mock(), mock())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
client.get("/inbox").let {
|
client.get("/inbox").let {
|
||||||
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
|
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
|
||||||
|
@ -45,18 +47,14 @@ class InboxRoutingKtTest {
|
||||||
val activityPubService = mock<ActivityPubService> {
|
val activityPubService = mock<ActivityPubService> {
|
||||||
on { parseActivity(any()) } doThrow JsonParseException()
|
on { parseActivity(any()) } doThrow JsonParseException()
|
||||||
}
|
}
|
||||||
val userService = mock<IUserService>()
|
mock<IUserService>()
|
||||||
val activityPubUserService = mock<ActivityPubUserService>()
|
mock<ActivityPubUserService>()
|
||||||
application {
|
application {
|
||||||
configureStatusPages()
|
configureStatusPages()
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
configureRouting(
|
routing {
|
||||||
httpSignatureVerifyService,
|
inbox(httpSignatureVerifyService, activityPubService)
|
||||||
activityPubService,
|
}
|
||||||
userService,
|
|
||||||
activityPubUserService,
|
|
||||||
mock()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
client.post("/inbox").let {
|
client.post("/inbox").let {
|
||||||
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
|
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
|
||||||
|
@ -70,7 +68,9 @@ class InboxRoutingKtTest {
|
||||||
}
|
}
|
||||||
application {
|
application {
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
configureRouting(mock(), mock(), mock(), mock(), mock())
|
routing {
|
||||||
|
inbox(mock(), mock())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
client.get("/users/test/inbox").let {
|
client.get("/users/test/inbox").let {
|
||||||
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
|
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
|
||||||
|
@ -88,18 +88,14 @@ class InboxRoutingKtTest {
|
||||||
val activityPubService = mock<ActivityPubService> {
|
val activityPubService = mock<ActivityPubService> {
|
||||||
on { parseActivity(any()) } doThrow JsonParseException()
|
on { parseActivity(any()) } doThrow JsonParseException()
|
||||||
}
|
}
|
||||||
val userService = mock<IUserService>()
|
mock<IUserService>()
|
||||||
val activityPubUserService = mock<ActivityPubUserService>()
|
mock<ActivityPubUserService>()
|
||||||
application {
|
application {
|
||||||
configureStatusPages()
|
configureStatusPages()
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
configureRouting(
|
routing {
|
||||||
httpSignatureVerifyService,
|
inbox(httpSignatureVerifyService, activityPubService)
|
||||||
activityPubService,
|
}
|
||||||
userService,
|
|
||||||
activityPubUserService,
|
|
||||||
mock()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
client.post("/users/test/inbox").let {
|
client.post("/users/test/inbox").let {
|
||||||
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
|
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
|
||||||
|
|
|
@ -10,18 +10,16 @@ import dev.usbharu.hideout.domain.model.ap.Image
|
||||||
import dev.usbharu.hideout.domain.model.ap.Key
|
import dev.usbharu.hideout.domain.model.ap.Key
|
||||||
import dev.usbharu.hideout.domain.model.ap.Person
|
import dev.usbharu.hideout.domain.model.ap.Person
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
import dev.usbharu.hideout.plugins.configureRouting
|
|
||||||
import dev.usbharu.hideout.plugins.configureSerialization
|
import dev.usbharu.hideout.plugins.configureSerialization
|
||||||
import dev.usbharu.hideout.service.activitypub.ActivityPubService
|
|
||||||
import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
|
import dev.usbharu.hideout.service.activitypub.ActivityPubUserService
|
||||||
import dev.usbharu.hideout.service.impl.IUserService
|
import dev.usbharu.hideout.service.impl.IUserService
|
||||||
import dev.usbharu.hideout.service.signature.HttpSignatureVerifyService
|
|
||||||
import dev.usbharu.hideout.util.HttpUtil.Activity
|
import dev.usbharu.hideout.util.HttpUtil.Activity
|
||||||
import dev.usbharu.hideout.util.HttpUtil.JsonLd
|
import dev.usbharu.hideout.util.HttpUtil.JsonLd
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.statement.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.server.config.*
|
import io.ktor.server.config.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
import io.ktor.server.testing.*
|
import io.ktor.server.testing.*
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.ArgumentMatchers.anyString
|
import org.mockito.ArgumentMatchers.anyString
|
||||||
|
@ -64,8 +62,6 @@ class UsersAPTest {
|
||||||
)
|
)
|
||||||
person.context = listOf("https://www.w3.org/ns/activitystreams")
|
person.context = listOf("https://www.w3.org/ns/activitystreams")
|
||||||
|
|
||||||
val httpSignatureVerifyService = mock<HttpSignatureVerifyService> {}
|
|
||||||
val activityPubService = mock<ActivityPubService> {}
|
|
||||||
val userService = mock<IUserService> {}
|
val userService = mock<IUserService> {}
|
||||||
|
|
||||||
val activityPubUserService = mock<ActivityPubUserService> {
|
val activityPubUserService = mock<ActivityPubUserService> {
|
||||||
|
@ -74,13 +70,9 @@ class UsersAPTest {
|
||||||
|
|
||||||
application {
|
application {
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
configureRouting(
|
routing {
|
||||||
httpSignatureVerifyService,
|
usersAP(activityPubUserService, userService)
|
||||||
activityPubService,
|
}
|
||||||
userService,
|
|
||||||
activityPubUserService,
|
|
||||||
mock()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
client.get("/users/test") {
|
client.get("/users/test") {
|
||||||
accept(ContentType.Application.Activity)
|
accept(ContentType.Application.Activity)
|
||||||
|
@ -130,8 +122,6 @@ class UsersAPTest {
|
||||||
)
|
)
|
||||||
person.context = listOf("https://www.w3.org/ns/activitystreams")
|
person.context = listOf("https://www.w3.org/ns/activitystreams")
|
||||||
|
|
||||||
val httpSignatureVerifyService = mock<HttpSignatureVerifyService> {}
|
|
||||||
val activityPubService = mock<ActivityPubService> {}
|
|
||||||
val userService = mock<IUserService> {}
|
val userService = mock<IUserService> {}
|
||||||
|
|
||||||
val activityPubUserService = mock<ActivityPubUserService> {
|
val activityPubUserService = mock<ActivityPubUserService> {
|
||||||
|
@ -140,13 +130,9 @@ class UsersAPTest {
|
||||||
|
|
||||||
application {
|
application {
|
||||||
configureSerialization()
|
configureSerialization()
|
||||||
configureRouting(
|
routing {
|
||||||
httpSignatureVerifyService,
|
usersAP(activityPubUserService, userService)
|
||||||
activityPubService,
|
}
|
||||||
userService,
|
|
||||||
activityPubUserService,
|
|
||||||
mock()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
client.get("/users/test") {
|
client.get("/users/test") {
|
||||||
accept(ContentType.Application.JsonLd)
|
accept(ContentType.Application.JsonLd)
|
||||||
|
@ -205,13 +191,9 @@ class UsersAPTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
application {
|
application {
|
||||||
configureRouting(
|
routing {
|
||||||
mock(),
|
usersAP(mock(), userService)
|
||||||
mock(),
|
}
|
||||||
userService,
|
|
||||||
mock(),
|
|
||||||
mock()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
client.get("/users/test") {
|
client.get("/users/test") {
|
||||||
accept(ContentType.Text.Html)
|
accept(ContentType.Text.Html)
|
||||||
|
|
|
@ -0,0 +1,619 @@
|
||||||
|
package dev.usbharu.hideout.routing.api.internal.v1
|
||||||
|
|
||||||
|
import com.auth0.jwt.interfaces.Claim
|
||||||
|
import com.auth0.jwt.interfaces.Payload
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.config.Config
|
||||||
|
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.Visibility
|
||||||
|
import dev.usbharu.hideout.plugins.TOKEN_AUTH
|
||||||
|
import dev.usbharu.hideout.plugins.configureSecurity
|
||||||
|
import dev.usbharu.hideout.plugins.configureSerialization
|
||||||
|
import dev.usbharu.hideout.service.IPostService
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.server.config.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.testing.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.JsonObjectMapper
|
||||||
|
import java.time.Instant
|
||||||
|
import kotlin.test.assertContentEquals
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PostsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 認証情報無しでpostsにGETしたらPUBLICな投稿一覧が返ってくる() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val posts = listOf(
|
||||||
|
Post(
|
||||||
|
id = 12345,
|
||||||
|
userId = 4321,
|
||||||
|
text = "test1",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/1"
|
||||||
|
),
|
||||||
|
Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 4322,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking {
|
||||||
|
findAll(
|
||||||
|
since = anyOrNull(),
|
||||||
|
until = anyOrNull(),
|
||||||
|
minId = anyOrNull(),
|
||||||
|
maxId = anyOrNull(),
|
||||||
|
limit = anyOrNull(),
|
||||||
|
userId = isNull()
|
||||||
|
)
|
||||||
|
} doReturn posts
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/posts").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertContentEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun 認証情報ありでpostsにGETすると権限のある投稿が返ってくる() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val claim = mock<Claim> {
|
||||||
|
on { asLong() } doReturn 1234
|
||||||
|
}
|
||||||
|
val payload = mock<Payload> {
|
||||||
|
on { getClaim(eq("uid")) } doReturn claim
|
||||||
|
}
|
||||||
|
|
||||||
|
val posts = listOf(
|
||||||
|
Post(
|
||||||
|
id = 12345,
|
||||||
|
userId = 4321,
|
||||||
|
text = "test1",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/1"
|
||||||
|
),
|
||||||
|
Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 4322,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
),
|
||||||
|
Post(
|
||||||
|
id = 1234567,
|
||||||
|
userId = 4333,
|
||||||
|
text = "Followers only",
|
||||||
|
visibility = Visibility.FOLLOWERS,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/3"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking {
|
||||||
|
findAll(
|
||||||
|
since = anyOrNull(),
|
||||||
|
until = anyOrNull(),
|
||||||
|
minId = anyOrNull(),
|
||||||
|
maxId = anyOrNull(),
|
||||||
|
limit = anyOrNull(),
|
||||||
|
userId = isNotNull()
|
||||||
|
)
|
||||||
|
} doReturn posts
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
authentication {
|
||||||
|
bearer(TOKEN_AUTH) {
|
||||||
|
authenticate {
|
||||||
|
JWTPrincipal(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configureSerialization()
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.get("/api/internal/v1/posts") {
|
||||||
|
header("Authorization", "Bearer asdkaf")
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `posts id にGETしたらPUBLICな投稿を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val post = Post(
|
||||||
|
12345, 1234, text = "aaa", visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1"
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking { findByIdForUser(any(), anyOrNull()) } doReturn post
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.get("/api/internal/v1/posts/1").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `認証情報ありでposts id にGETしたら権限のある投稿を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val post = Post(
|
||||||
|
12345, 1234, text = "aaa", visibility = Visibility.FOLLOWERS,
|
||||||
|
createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1"
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking { findByIdForUser(any(), isNotNull()) } doReturn post
|
||||||
|
}
|
||||||
|
val claim = mock<Claim> {
|
||||||
|
on { asLong() } doReturn 1234
|
||||||
|
}
|
||||||
|
val payload = mock<Payload> {
|
||||||
|
on { getClaim(eq("uid")) } doReturn claim
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
authentication {
|
||||||
|
bearer(TOKEN_AUTH) {
|
||||||
|
authenticate {
|
||||||
|
JWTPrincipal(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.get("/api/internal/v1/posts/1") {
|
||||||
|
header("Authorization", "Bearer asdkaf")
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `posts-post postsにpostしたら投稿できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val claim = mock<Claim> {
|
||||||
|
on { asLong() } doReturn 1234
|
||||||
|
}
|
||||||
|
val payload = mock<Payload> {
|
||||||
|
on { getClaim(eq("uid")) } doReturn claim
|
||||||
|
}
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking { create(any<PostCreateDto>()) } doAnswer {
|
||||||
|
val argument = it.getArgument<PostCreateDto>(0)
|
||||||
|
Post(
|
||||||
|
123L,
|
||||||
|
argument.userId,
|
||||||
|
null,
|
||||||
|
argument.text,
|
||||||
|
Instant.now().toEpochMilli(),
|
||||||
|
Visibility.PUBLIC,
|
||||||
|
"https://example.com"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
authentication {
|
||||||
|
|
||||||
|
bearer(TOKEN_AUTH) {
|
||||||
|
authenticate {
|
||||||
|
println("aaaaaaaaaaaa")
|
||||||
|
JWTPrincipal(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
configureSerialization()
|
||||||
|
}
|
||||||
|
|
||||||
|
val post = dev.usbharu.hideout.domain.model.hideout.form.Post("test")
|
||||||
|
client.post("/api/internal/v1/posts") {
|
||||||
|
header("Authorization", "Bearer asdkaf")
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(Config.configData.objectMapper.writeValueAsString(post))
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals("https://example.com", headers["Location"])
|
||||||
|
}
|
||||||
|
argumentCaptor<PostCreateDto> {
|
||||||
|
verify(postService).create(capture())
|
||||||
|
assertEquals(PostCreateDto("test", userId = 1234), firstValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users userId postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val posts = listOf(
|
||||||
|
Post(
|
||||||
|
id = 12345,
|
||||||
|
userId = 1,
|
||||||
|
text = "test1",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/1"
|
||||||
|
),
|
||||||
|
Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 1,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking {
|
||||||
|
findByUserIdForUser(
|
||||||
|
userId = any(),
|
||||||
|
since = anyOrNull(),
|
||||||
|
until = anyOrNull(),
|
||||||
|
minId = anyOrNull(),
|
||||||
|
maxId = anyOrNull(),
|
||||||
|
limit = anyOrNull(),
|
||||||
|
forUserId = anyOrNull()
|
||||||
|
)
|
||||||
|
} doReturn posts
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/1/posts").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users username postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val posts = listOf(
|
||||||
|
Post(
|
||||||
|
id = 12345,
|
||||||
|
userId = 1,
|
||||||
|
text = "test1",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/1"
|
||||||
|
),
|
||||||
|
Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 1,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking {
|
||||||
|
findByUserNameAndDomainForUser(
|
||||||
|
userName = eq("test1"),
|
||||||
|
domain = eq(Config.configData.domain),
|
||||||
|
since = anyOrNull(),
|
||||||
|
until = anyOrNull(),
|
||||||
|
minId = anyOrNull(),
|
||||||
|
maxId = anyOrNull(),
|
||||||
|
limit = anyOrNull(),
|
||||||
|
forUserId = anyOrNull()
|
||||||
|
)
|
||||||
|
} doReturn posts
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/test1/posts").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users username@domain postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val posts = listOf(
|
||||||
|
Post(
|
||||||
|
id = 12345,
|
||||||
|
userId = 1,
|
||||||
|
text = "test1",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/1"
|
||||||
|
),
|
||||||
|
Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 1,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking {
|
||||||
|
findByUserNameAndDomainForUser(
|
||||||
|
userName = eq("test1"),
|
||||||
|
domain = eq("example.com"),
|
||||||
|
since = anyOrNull(),
|
||||||
|
until = anyOrNull(),
|
||||||
|
minId = anyOrNull(),
|
||||||
|
maxId = anyOrNull(),
|
||||||
|
limit = anyOrNull(),
|
||||||
|
forUserId = anyOrNull()
|
||||||
|
)
|
||||||
|
} doReturn posts
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/test1@example.com/posts").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users @username@domain postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val posts = listOf(
|
||||||
|
Post(
|
||||||
|
id = 12345,
|
||||||
|
userId = 1,
|
||||||
|
text = "test1",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/1"
|
||||||
|
),
|
||||||
|
Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 1,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking {
|
||||||
|
findByUserNameAndDomainForUser(
|
||||||
|
userName = eq("test1"),
|
||||||
|
domain = eq("example.com"),
|
||||||
|
since = anyOrNull(),
|
||||||
|
until = anyOrNull(),
|
||||||
|
minId = anyOrNull(),
|
||||||
|
maxId = anyOrNull(),
|
||||||
|
limit = anyOrNull(),
|
||||||
|
forUserId = anyOrNull()
|
||||||
|
)
|
||||||
|
} doReturn posts
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/@test1@example.com/posts").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name posts id にGETしたらPUBLICな投稿を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val post = Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 1,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking { findByIdForUser(eq(12345L), anyOrNull()) } doReturn post
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/test/posts/12345").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users id posts id にGETしたらPUBLICな投稿を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val post = Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 1,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking { findByIdForUser(eq(12345L), anyOrNull()) } doReturn post
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/1/posts/12345").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name posts id にGETしたらUserIdが間違っててもPUBLICな投稿を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val post = Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 1,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking { findByIdForUser(eq(12345L), anyOrNull()) } doReturn post
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/423827849732847/posts/12345").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name posts id にGETしたらuserNameが間違っててもPUBLICな投稿を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val post = Post(
|
||||||
|
id = 123456,
|
||||||
|
userId = 1,
|
||||||
|
text = "test2",
|
||||||
|
visibility = Visibility.PUBLIC,
|
||||||
|
createdAt = Instant.now().toEpochMilli(),
|
||||||
|
url = "https://example.com/posts/2"
|
||||||
|
)
|
||||||
|
val postService = mock<IPostService> {
|
||||||
|
onBlocking { findByIdForUser(eq(12345L), anyOrNull()) } doReturn post
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
posts(postService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/invalidUserName/posts/12345").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,691 @@
|
||||||
|
package dev.usbharu.hideout.routing.api.internal.v1
|
||||||
|
|
||||||
|
import com.auth0.jwt.interfaces.Claim
|
||||||
|
import com.auth0.jwt.interfaces.Payload
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.config.Config
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.form.UserCreate
|
||||||
|
import dev.usbharu.hideout.plugins.TOKEN_AUTH
|
||||||
|
import dev.usbharu.hideout.plugins.configureSecurity
|
||||||
|
import dev.usbharu.hideout.plugins.configureSerialization
|
||||||
|
import dev.usbharu.hideout.service.IUserApiService
|
||||||
|
import dev.usbharu.hideout.service.impl.IUserService
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.server.config.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.testing.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.JsonObjectMapper
|
||||||
|
import java.time.Instant
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class UsersTest {
|
||||||
|
@Test
|
||||||
|
fun `users にGETするとユーザー一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val users = listOf(
|
||||||
|
UserResponse(
|
||||||
|
12345,
|
||||||
|
"test1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
),
|
||||||
|
UserResponse(
|
||||||
|
12343,
|
||||||
|
"tes2",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"",
|
||||||
|
"https://example.com/tes2",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val userService = mock<IUserApiService> {
|
||||||
|
onBlocking { findAll(anyOrNull(), anyOrNull()) } doReturn users
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.get("/api/internal/v1/users").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(users, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users にPOSTすると新規ユーザー作成ができる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val userCreateDto = UserCreate("test", "XXXXXXX")
|
||||||
|
val userService = mock<IUserService> {
|
||||||
|
onBlocking { usernameAlreadyUse(any()) } doReturn false
|
||||||
|
onBlocking { createLocalUser(any()) } doReturn User(
|
||||||
|
id = 12345,
|
||||||
|
name = "test",
|
||||||
|
domain = "example.com",
|
||||||
|
screenName = "testUser",
|
||||||
|
description = "test user",
|
||||||
|
password = "XXXXXXX",
|
||||||
|
inbox = "https://example.com/inbox",
|
||||||
|
outbox = "https://example.com/outbox",
|
||||||
|
url = "https://example.com",
|
||||||
|
publicKey = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
|
||||||
|
privateKey = "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----",
|
||||||
|
createdAt = Instant.now()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(userService, mock())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.post("/api/internal/v1/users") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(JsonObjectMapper.objectMapper.writeValueAsString(userCreateDto))
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.Created, status)
|
||||||
|
assertEquals(
|
||||||
|
"${Config.configData.url}/api/internal/v1/users/${userCreateDto.username}",
|
||||||
|
headers["Location"]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users 既にユーザー名が使用されているときはBadRequestが帰ってくる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val userCreateDto = UserCreate("test", "XXXXXXX")
|
||||||
|
val userService = mock<IUserService> {
|
||||||
|
onBlocking { usernameAlreadyUse(any()) } doReturn true
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(userService, mock())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.post("/api/internal/v1/users") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(JsonObjectMapper.objectMapper.writeValueAsString(userCreateDto))
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.BadRequest, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name にGETしたらユーザーを取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val userResponse = UserResponse(
|
||||||
|
1234,
|
||||||
|
"test1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findByAcct(any()) } doReturn userResponse
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/test1").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users id にGETしたらユーザーを取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val userResponse = UserResponse(
|
||||||
|
1234,
|
||||||
|
"test1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findById(any()) } doReturn userResponse
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/1234").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name@domain にGETしたらユーザーを取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val userResponse = UserResponse(
|
||||||
|
1234,
|
||||||
|
"test1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findByAcct(any()) } doReturn userResponse
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/test1").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users @name@domain にGETしたらユーザーを取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
val userResponse = UserResponse(
|
||||||
|
1234,
|
||||||
|
"test1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findByAcct(any()) } doReturn userResponse
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/test1").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name followers にGETしたらフォロワー一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val followers = listOf(
|
||||||
|
UserResponse(
|
||||||
|
1235,
|
||||||
|
"follower1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
), UserResponse(
|
||||||
|
1236,
|
||||||
|
"follower2",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findFollowersByAcct(any()) } doReturn followers
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/test1/followers").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name@domain followers にGETしたらフォロワー一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val followers = listOf(
|
||||||
|
UserResponse(
|
||||||
|
1235,
|
||||||
|
"follower1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
), UserResponse(
|
||||||
|
1236,
|
||||||
|
"follower2",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findFollowersByAcct(any()) } doReturn followers
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/@test1@example.com/followers").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users id followers にGETしたらフォロワー一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val followers = listOf(
|
||||||
|
UserResponse(
|
||||||
|
1235,
|
||||||
|
"follower1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
), UserResponse(
|
||||||
|
1236,
|
||||||
|
"follower2",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findFollowers(any()) } doReturn followers
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/1234/followers").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name followers に認証情報ありでGETしたらフォローできる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val claim = mock<Claim> {
|
||||||
|
on { asLong() } doReturn 1234
|
||||||
|
}
|
||||||
|
val payload = mock<Payload> {
|
||||||
|
on { getClaim(eq("uid")) } doReturn claim
|
||||||
|
}
|
||||||
|
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findByAcct(any()) } doReturn UserResponse(
|
||||||
|
1235,
|
||||||
|
"follower1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val userService = mock<IUserService> {
|
||||||
|
onBlocking { addFollowers(eq(1235), eq(1234)) } doReturn true
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
authentication {
|
||||||
|
bearer(TOKEN_AUTH) {
|
||||||
|
authenticate {
|
||||||
|
JWTPrincipal(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(userService, userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.post("/api/internal/v1/users/test1/followers") {
|
||||||
|
header(HttpHeaders.Authorization, "Bearer test")
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name followers に認証情報ありでGETしたらフォロー処理受付になることもある`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val claim = mock<Claim> {
|
||||||
|
on { asLong() } doReturn 1234
|
||||||
|
}
|
||||||
|
val payload = mock<Payload> {
|
||||||
|
on { getClaim(eq("uid")) } doReturn claim
|
||||||
|
}
|
||||||
|
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findByAcct(any()) } doReturn UserResponse(
|
||||||
|
1235,
|
||||||
|
"follower1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val userService = mock<IUserService> {
|
||||||
|
onBlocking { addFollowers(eq(1235), eq(1234)) } doReturn false
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
authentication {
|
||||||
|
bearer(TOKEN_AUTH) {
|
||||||
|
authenticate {
|
||||||
|
JWTPrincipal(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(userService, userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.post("/api/internal/v1/users/test1/followers") {
|
||||||
|
header(HttpHeaders.Authorization, "Bearer test")
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.Accepted, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users id followers に認証情報ありでGETしたらフォロー処理受付になることもある`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val claim = mock<Claim> {
|
||||||
|
on { asLong() } doReturn 1234
|
||||||
|
}
|
||||||
|
val payload = mock<Payload> {
|
||||||
|
on { getClaim(eq("uid")) } doReturn claim
|
||||||
|
}
|
||||||
|
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findById(any()) } doReturn UserResponse(
|
||||||
|
1235,
|
||||||
|
"follower1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val userService = mock<IUserService> {
|
||||||
|
onBlocking { addFollowers(eq(1235), eq(1234)) } doReturn false
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
authentication {
|
||||||
|
bearer(TOKEN_AUTH) {
|
||||||
|
authenticate {
|
||||||
|
JWTPrincipal(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(userService, userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.post("/api/internal/v1/users/1235/followers") {
|
||||||
|
header(HttpHeaders.Authorization, "Bearer test")
|
||||||
|
}.apply {
|
||||||
|
assertEquals(HttpStatusCode.Accepted, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name following にGETしたらフォロイー一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val followers = listOf(
|
||||||
|
UserResponse(
|
||||||
|
1235,
|
||||||
|
"follower1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
), UserResponse(
|
||||||
|
1236,
|
||||||
|
"follower2",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findFollowingsByAcct(any()) } doReturn followers
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/test1/following").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users name@domain following にGETしたらフォロイー一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val followers = listOf(
|
||||||
|
UserResponse(
|
||||||
|
1235,
|
||||||
|
"follower1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
), UserResponse(
|
||||||
|
1236,
|
||||||
|
"follower2",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findFollowingsByAcct(any()) } doReturn followers
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/test1@domain/following").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `users id following にGETしたらフォロイー一覧を取得できる`() = testApplication {
|
||||||
|
environment {
|
||||||
|
config = ApplicationConfig("empty.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
val followers = listOf(
|
||||||
|
UserResponse(
|
||||||
|
1235,
|
||||||
|
"follower1",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
), UserResponse(
|
||||||
|
1236,
|
||||||
|
"follower2",
|
||||||
|
"example.com",
|
||||||
|
"test",
|
||||||
|
"test User",
|
||||||
|
"https://example.com/test",
|
||||||
|
Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val userApiService = mock<IUserApiService> {
|
||||||
|
onBlocking { findFollowings(any()) } doReturn followers
|
||||||
|
}
|
||||||
|
application {
|
||||||
|
configureSerialization()
|
||||||
|
configureSecurity(mock(), mock(), mock(), mock(), mock())
|
||||||
|
routing {
|
||||||
|
route("/api/internal/v1") {
|
||||||
|
users(mock(), userApiService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.get("/api/internal/v1/users/1234/following").apply {
|
||||||
|
assertEquals(HttpStatusCode.OK, status)
|
||||||
|
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -115,7 +115,7 @@ class ActivityPubFollowServiceImplTest {
|
||||||
createdAt = Instant.now()
|
createdAt = Instant.now()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
onBlocking { addFollowers(any(), any()) } doReturn Unit
|
onBlocking { addFollowers(any(), any()) } doReturn false
|
||||||
}
|
}
|
||||||
val activityPubFollowService =
|
val activityPubFollowService =
|
||||||
ActivityPubFollowServiceImpl(
|
ActivityPubFollowServiceImpl(
|
||||||
|
|
|
@ -5,8 +5,9 @@ package dev.usbharu.hideout.service.activitypub
|
||||||
|
|
||||||
import dev.usbharu.hideout.config.Config
|
import dev.usbharu.hideout.config.Config
|
||||||
import dev.usbharu.hideout.config.ConfigData
|
import dev.usbharu.hideout.config.ConfigData
|
||||||
import dev.usbharu.hideout.domain.model.PostEntity
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
import dev.usbharu.hideout.domain.model.job.DeliverPostJob
|
||||||
import dev.usbharu.hideout.service.impl.IUserService
|
import dev.usbharu.hideout.service.impl.IUserService
|
||||||
import dev.usbharu.hideout.service.job.JobQueueParentService
|
import dev.usbharu.hideout.service.job.JobQueueParentService
|
||||||
|
@ -72,13 +73,13 @@ class ActivityPubNoteServiceImplTest {
|
||||||
}
|
}
|
||||||
val jobQueueParentService = mock<JobQueueParentService>()
|
val jobQueueParentService = mock<JobQueueParentService>()
|
||||||
val activityPubNoteService = ActivityPubNoteServiceImpl(mock(), jobQueueParentService, userService)
|
val activityPubNoteService = ActivityPubNoteServiceImpl(mock(), jobQueueParentService, userService)
|
||||||
val postEntity = PostEntity(
|
val postEntity = Post(
|
||||||
1L,
|
1L,
|
||||||
1L,
|
1L,
|
||||||
null,
|
null,
|
||||||
"test text",
|
"test text",
|
||||||
1L,
|
1L,
|
||||||
1,
|
Visibility.PUBLIC,
|
||||||
"https://example.com"
|
"https://example.com"
|
||||||
)
|
)
|
||||||
activityPubNoteService.createNote(postEntity)
|
activityPubNoteService.createNote(postEntity)
|
||||||
|
@ -99,8 +100,14 @@ class ActivityPubNoteServiceImplTest {
|
||||||
JobProps(
|
JobProps(
|
||||||
data = mapOf<String, Any>(
|
data = mapOf<String, Any>(
|
||||||
DeliverPostJob.actor.name to "https://follower.example.com",
|
DeliverPostJob.actor.name to "https://follower.example.com",
|
||||||
DeliverPostJob.post.name to "{\"id\":1,\"userId\":1,\"inReplyToId\":null,\"text\":\"test text\"," +
|
DeliverPostJob.post.name to """{
|
||||||
"\"createdAt\":1,\"updatedAt\":1,\"url\":\"https://example.com\"}",
|
"id": 1,
|
||||||
|
"userId": 1,
|
||||||
|
"text": "test text",
|
||||||
|
"createdAt": 132525324,
|
||||||
|
"visibility": 0,
|
||||||
|
"url": "https://example.com"
|
||||||
|
}""",
|
||||||
DeliverPostJob.inbox.name to "https://follower.example.com/inbox"
|
DeliverPostJob.inbox.name to "https://follower.example.com/inbox"
|
||||||
),
|
),
|
||||||
json = Json
|
json = Json
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
|
||||||
|
package dev.usbharu.hideout.service.impl
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
|
import dev.usbharu.hideout.repository.Posts
|
||||||
|
import dev.usbharu.hideout.repository.UsersFollowers
|
||||||
|
import dev.usbharu.hideout.service.TwitterSnowflakeIdGenerateService
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.jetbrains.exposed.sql.SchemaUtils
|
||||||
|
import org.jetbrains.exposed.sql.batchInsert
|
||||||
|
import org.jetbrains.exposed.sql.insert
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import java.time.Instant
|
||||||
|
import kotlin.test.assertContentEquals
|
||||||
|
|
||||||
|
class PostServiceTest {
|
||||||
|
|
||||||
|
lateinit var db: Database
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
db = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
|
||||||
|
transaction(db) {
|
||||||
|
SchemaUtils.create(Posts)
|
||||||
|
connection.prepareStatement("SET REFERENTIAL_INTEGRITY FALSE", false).executeUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
transaction(db) {
|
||||||
|
SchemaUtils.drop(Posts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `findAll 公開投稿を取得できる`() = runTest {
|
||||||
|
val postService = PostService(mock(), mock(), mock())
|
||||||
|
|
||||||
|
suspend fun createPost(userId: Long, text: String, visibility: Visibility = Visibility.PUBLIC): Post {
|
||||||
|
return Post(
|
||||||
|
TwitterSnowflakeIdGenerateService.generateId(),
|
||||||
|
userId,
|
||||||
|
null,
|
||||||
|
text,
|
||||||
|
Instant.now().toEpochMilli(),
|
||||||
|
visibility,
|
||||||
|
"https://example.com"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val userA: Long = 1
|
||||||
|
val userB: Long = 2
|
||||||
|
|
||||||
|
val posts = listOf(
|
||||||
|
createPost(userA, "hello"),
|
||||||
|
createPost(userA, "hello1"),
|
||||||
|
createPost(userA, "hello2"),
|
||||||
|
createPost(userA, "hello3"),
|
||||||
|
createPost(userA, "hello4"),
|
||||||
|
createPost(userA, "hello5"),
|
||||||
|
createPost(userA, "hello6"),
|
||||||
|
createPost(userB, "good bay ", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay1", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay2", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay3", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay4", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay5", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay6", Visibility.FOLLOWERS),
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
Posts.batchInsert(posts) {
|
||||||
|
this[Posts.id] = it.id
|
||||||
|
this[Posts.userId] = it.userId
|
||||||
|
this[Posts.overview] = it.overview
|
||||||
|
this[Posts.text] = it.text
|
||||||
|
this[Posts.createdAt] = it.createdAt
|
||||||
|
this[Posts.visibility] = it.visibility.ordinal
|
||||||
|
this[Posts.url] = it.url
|
||||||
|
this[Posts.replyId] = it.replyId
|
||||||
|
this[Posts.repostId] = it.repostId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val expect = posts.filter { it.visibility == Visibility.PUBLIC }
|
||||||
|
|
||||||
|
val actual = postService.findAll()
|
||||||
|
assertContentEquals(expect, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `findAll フォロー限定投稿を見れる`() = runTest {
|
||||||
|
val postService = PostService(mock(), mock(), mock())
|
||||||
|
|
||||||
|
suspend fun createPost(userId: Long, text: String, visibility: Visibility = Visibility.PUBLIC): Post {
|
||||||
|
return Post(
|
||||||
|
TwitterSnowflakeIdGenerateService.generateId(),
|
||||||
|
userId,
|
||||||
|
null,
|
||||||
|
text,
|
||||||
|
Instant.now().toEpochMilli(),
|
||||||
|
visibility,
|
||||||
|
"https://example.com"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val userA: Long = 1
|
||||||
|
val userB: Long = 2
|
||||||
|
|
||||||
|
val posts = listOf(
|
||||||
|
createPost(userA, "hello"),
|
||||||
|
createPost(userA, "hello1"),
|
||||||
|
createPost(userA, "hello2"),
|
||||||
|
createPost(userA, "hello3"),
|
||||||
|
createPost(userA, "hello4"),
|
||||||
|
createPost(userA, "hello5"),
|
||||||
|
createPost(userA, "hello6"),
|
||||||
|
createPost(userB, "good bay ", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay1", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay2", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay3", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay4", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay5", Visibility.FOLLOWERS),
|
||||||
|
createPost(userB, "good bay6", Visibility.FOLLOWERS),
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction(db) {
|
||||||
|
SchemaUtils.create(UsersFollowers)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
Posts.batchInsert(posts) {
|
||||||
|
this[Posts.id] = it.id
|
||||||
|
this[Posts.userId] = it.userId
|
||||||
|
this[Posts.overview] = it.overview
|
||||||
|
this[Posts.text] = it.text
|
||||||
|
this[Posts.createdAt] = it.createdAt
|
||||||
|
this[Posts.visibility] = it.visibility.ordinal
|
||||||
|
this[Posts.url] = it.url
|
||||||
|
this[Posts.replyId] = it.replyId
|
||||||
|
this[Posts.repostId] = it.repostId
|
||||||
|
}
|
||||||
|
UsersFollowers.insert {
|
||||||
|
it[id] = 100L
|
||||||
|
it[userId] = userB
|
||||||
|
it[followerId] = userA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val actual = postService.findAll(userId = userA)
|
||||||
|
assertContentEquals(posts, actual)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue