mirror of https://github.com/usbharu/Hideout.git
feat: QueryServiceの認可処理を追加して起動できるように
This commit is contained in:
parent
540fe0eaa5
commit
146b907c69
|
@ -17,12 +17,11 @@
|
|||
package dev.usbharu.hideout.core.application.actor
|
||||
|
||||
import dev.usbharu.hideout.core.application.exception.InternalServerException
|
||||
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
||||
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
|
||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.FromApi
|
||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
|
@ -34,10 +33,10 @@ class GetUserDetailApplicationService(
|
|||
private val customEmojiRepository: CustomEmojiRepository,
|
||||
transaction: Transaction,
|
||||
) :
|
||||
AbstractApplicationService<GetUserDetail, UserDetail>(transaction, Companion.logger) {
|
||||
override suspend fun internalExecute(command: GetUserDetail, principal: Principal): UserDetail {
|
||||
val userDetail = userDetailRepository.findById(UserDetailId(command.id))
|
||||
?: throw IllegalArgumentException("User ${command.id} does not exist")
|
||||
LocalUserAbstractApplicationService<Unit, UserDetail>(transaction, Companion.logger) {
|
||||
override suspend fun internalExecute(command: Unit, principal: FromApi): UserDetail {
|
||||
val userDetail = userDetailRepository.findById(principal.userDetailId)
|
||||
?: throw IllegalArgumentException("User ${principal.userDetailId} does not exist")
|
||||
val actor = actorRepository.findById(userDetail.actorId)
|
||||
?: throw InternalServerException("Actor ${userDetail.actorId} not found")
|
||||
|
||||
|
@ -47,6 +46,6 @@ class GetUserDetailApplicationService(
|
|||
}
|
||||
|
||||
companion object {
|
||||
val logger = LoggerFactory.getLogger(GetUserDetailApplicationService::class.java)
|
||||
private val logger = LoggerFactory.getLogger(GetUserDetailApplicationService::class.java)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package dev.usbharu.hideout.core.application.shared
|
||||
|
||||
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.FromApi
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import org.slf4j.Logger
|
||||
|
@ -7,7 +8,9 @@ import org.slf4j.Logger
|
|||
abstract class LocalUserAbstractApplicationService<T : Any, R>(transaction: Transaction, logger: Logger) :
|
||||
AbstractApplicationService<T, R>(transaction, logger) {
|
||||
override suspend fun internalExecute(command: T, principal: Principal): R {
|
||||
require(principal is FromApi)
|
||||
if (principal !is FromApi) {
|
||||
throw PermissionDeniedException()
|
||||
}
|
||||
return internalExecute(command, principal)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.createdAt
|
|||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.deleted
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.hide
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.id
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.instanceId
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.moveTo
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.overview
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.replyId
|
||||
|
@ -61,6 +62,7 @@ class ExposedPostRepository(
|
|||
Posts.upsert {
|
||||
it[id] = post.id.id
|
||||
it[actorId] = post.actorId.id
|
||||
it[instanceId] = post.instanceId.instanceId
|
||||
it[overview] = post.overview?.overview
|
||||
it[content] = post.content.content
|
||||
it[text] = post.content.text
|
||||
|
@ -106,6 +108,7 @@ class ExposedPostRepository(
|
|||
Posts.batchUpsert(posts, id) {
|
||||
this[id] = it.id.id
|
||||
this[actorId] = it.actorId.id
|
||||
this[instanceId] = it.instanceId.instanceId
|
||||
this[overview] = it.overview?.overview
|
||||
this[content] = it.content.content
|
||||
this[text] = it.content.text
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package dev.usbharu.hideout.core.infrastructure.springframework.oauth2
|
||||
|
||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||
import dev.usbharu.hideout.core.domain.model.support.acct.Acct
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.FromApi
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.PrincipalContextHolder
|
||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
||||
import dev.usbharu.hideout.core.query.principal.PrincipalQueryService
|
||||
|
@ -10,18 +13,24 @@ import org.springframework.security.oauth2.jwt.Jwt
|
|||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class SpringSecurityOauth2PrincipalContextHolder(private val principalQueryService: PrincipalQueryService) :
|
||||
class SpringSecurityOauth2PrincipalContextHolder(
|
||||
private val principalQueryService: PrincipalQueryService,
|
||||
private val transaction: Transaction
|
||||
) :
|
||||
PrincipalContextHolder {
|
||||
override suspend fun getPrincipal(): FromApi {
|
||||
val principal = SecurityContextHolder.getContext().authentication?.principal as Jwt
|
||||
override suspend fun getPrincipal(): Principal {
|
||||
val principal =
|
||||
SecurityContextHolder.getContext().authentication?.principal as? Jwt ?: return Anonymous
|
||||
|
||||
val id = principal.getClaim<String>("uid").toLong()
|
||||
val userDetail = principalQueryService.findByUserDetailId(UserDetailId(id))
|
||||
return transaction.transaction {
|
||||
val id = principal.getClaim<String>("uid").toLong()
|
||||
val userDetail = principalQueryService.findByUserDetailId(UserDetailId(id))
|
||||
|
||||
return FromApi(
|
||||
userDetail.actorId,
|
||||
userDetail.userDetailId,
|
||||
Acct(userDetail.username, userDetail.host)
|
||||
)
|
||||
return@transaction FromApi(
|
||||
userDetail.actorId,
|
||||
userDetail.userDetailId,
|
||||
Acct(userDetail.username, userDetail.host)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,6 +71,14 @@ create table if not exists actor_alsoknownas
|
|||
constraint fk_actor_alsoknownas_actors__also_known_as foreign key ("also_known_as") references actors (id) on delete cascade on update cascade
|
||||
);
|
||||
|
||||
create table timelines
|
||||
(
|
||||
id bigint primary key,
|
||||
user_detail_id bigint not null,
|
||||
name varchar(255) not null,
|
||||
visibility varchar(100) not null,
|
||||
is_system boolean not null default false
|
||||
);
|
||||
create table if not exists user_details
|
||||
(
|
||||
id bigserial primary key,
|
||||
|
@ -78,9 +86,14 @@ create table if not exists user_details
|
|||
password varchar(255) not null,
|
||||
auto_accept_followee_follow_request boolean not null,
|
||||
last_migration timestamp null default null,
|
||||
constraint fk_user_details_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict
|
||||
home_timeline_id bigint null default null,
|
||||
constraint fk_user_details_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict,
|
||||
constraint fk_user_details_timelines_id__id foreign key (home_timeline_id) references timelines (id) on delete cascade on update cascade
|
||||
);
|
||||
|
||||
alter table timelines
|
||||
add constraint fk_timelines_user_details__user_detail_id foreign key ("user_detail_id") references user_details (id) on delete cascade on update cascade;
|
||||
|
||||
create table if not exists media
|
||||
(
|
||||
id bigint primary key,
|
||||
|
@ -104,6 +117,7 @@ create table if not exists posts
|
|||
(
|
||||
id bigint primary key,
|
||||
actor_id bigint not null,
|
||||
instance_id bigint not null,
|
||||
overview varchar(100) null,
|
||||
content varchar(5000) not null,
|
||||
text varchar(3000) not null,
|
||||
|
@ -118,6 +132,8 @@ create table if not exists posts
|
|||
hide boolean default false not null,
|
||||
move_to bigint default null null
|
||||
);
|
||||
alter table posts
|
||||
add constraint fk_posts_instance_id__id foreign key (instance_id) references instance (id) on delete cascade on update cascade;
|
||||
alter table posts
|
||||
add constraint fk_posts_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict;
|
||||
alter table posts
|
||||
|
|
|
@ -27,10 +27,7 @@ import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
|
|||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status.Visibility.*
|
||||
import dev.usbharu.hideout.mastodon.query.StatusQuery
|
||||
import dev.usbharu.hideout.mastodon.query.StatusQueryService
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.andWhere
|
||||
import org.jetbrains.exposed.sql.leftJoin
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.net.URI
|
||||
import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia
|
||||
|
@ -39,6 +36,33 @@ import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.CustomEmoji a
|
|||
@Suppress("IncompleteDestructuring")
|
||||
@Repository
|
||||
class StatusQueryServiceImpl : StatusQueryService {
|
||||
|
||||
protected fun authorizedQuery(principal: Principal? = null): QueryAlias {
|
||||
if (principal == null) {
|
||||
return Posts
|
||||
.selectAll()
|
||||
.where {
|
||||
Posts.visibility eq Visibility.PUBLIC.name or (Posts.visibility eq Visibility.UNLISTED.name)
|
||||
}
|
||||
.alias("authorized_table")
|
||||
}
|
||||
|
||||
val relationshipsAlias = Relationships.alias("inverse_relationships")
|
||||
|
||||
return Posts
|
||||
.leftJoin(PostsVisibleActors)
|
||||
.leftJoin(Relationships, otherColumn = { actorId })
|
||||
.leftJoin(relationshipsAlias, otherColumn = { relationshipsAlias[Relationships.actorId] })
|
||||
.selectAll()
|
||||
.where {
|
||||
Posts.visibility eq Visibility.PUBLIC.name or
|
||||
(Posts.visibility eq Visibility.UNLISTED.name) or
|
||||
(Posts.visibility eq Visibility.DIRECT.name and (PostsVisibleActors.actorId eq principal.actorId.id)) or
|
||||
(Posts.visibility eq Visibility.FOLLOWERS.name and (Relationships.blocking eq false and (relationshipsAlias[Relationships.following] eq true)))
|
||||
}
|
||||
.alias("authorized_table")
|
||||
}
|
||||
|
||||
override suspend fun findByPostIds(ids: List<Long>): List<Status> = findByPostIdsWithMedia(ids)
|
||||
|
||||
override suspend fun findByPostIdsWithMediaIds(statusQueries: List<StatusQuery>): List<Status> {
|
||||
|
@ -50,10 +74,12 @@ class StatusQueryServiceImpl : StatusQueryService {
|
|||
val emojiIdSet = mutableSetOf<Long>()
|
||||
emojiIdSet.addAll(statusQueries.flatMap { it.emojiIds })
|
||||
|
||||
val qa = authorizedQuery()
|
||||
|
||||
val postMap = Posts
|
||||
.leftJoin(Actors)
|
||||
.selectAll().where { Posts.id inList postIdSet }
|
||||
.associate { it[Posts.id] to toStatus(it) }
|
||||
.associate { it[Posts.id] to toStatus(it, qa) }
|
||||
val mediaMap = Media.selectAll().where { Media.id inList mediaIdSet }
|
||||
.associate {
|
||||
it[Media.id] to it.toMedia().toMediaAttachments()
|
||||
|
@ -82,7 +108,8 @@ class StatusQueryServiceImpl : StatusQueryService {
|
|||
tagged: String?,
|
||||
includeFollowers: Boolean,
|
||||
): List<Status> {
|
||||
val query = Posts
|
||||
val qa = authorizedQuery()
|
||||
val query = qa
|
||||
.leftJoin(PostsMedia)
|
||||
.leftJoin(Actors)
|
||||
.leftJoin(Media)
|
||||
|
@ -107,7 +134,7 @@ class StatusQueryServiceImpl : StatusQueryService {
|
|||
.groupBy { it[Posts.id] }
|
||||
.map { it.value }
|
||||
.map {
|
||||
toStatus(it.first()).copy(
|
||||
toStatus(it.first(), qa).copy(
|
||||
mediaAttachments = it.mapNotNull { resultRow ->
|
||||
resultRow.toMediaOrNull()?.toMediaAttachments()
|
||||
}
|
||||
|
@ -119,21 +146,22 @@ class StatusQueryServiceImpl : StatusQueryService {
|
|||
}
|
||||
|
||||
override suspend fun findByPostId(id: Long, principal: Principal?): Status? {
|
||||
val map = Posts
|
||||
.leftJoin(PostsMedia)
|
||||
.leftJoin(Actors)
|
||||
.leftJoin(Media,{PostsMedia.mediaId},{Media.id})
|
||||
val aq = authorizedQuery(principal)
|
||||
val map = aq
|
||||
.leftJoin(PostsMedia, { aq[Posts.id] }, { PostsMedia.postId })
|
||||
.leftJoin(Actors, { aq[Posts.actorId] }, { Actors.id })
|
||||
.leftJoin(Media, { PostsMedia.mediaId }, { Media.id })
|
||||
.selectAll()
|
||||
.where { Posts.id eq id }
|
||||
.groupBy { it[Posts.id] }
|
||||
.where { aq[Posts.id] eq id }
|
||||
.groupBy { it[aq[Posts.id]] }
|
||||
.map { it.value }
|
||||
.map {
|
||||
toStatus(it.first()).copy(
|
||||
toStatus(it.first(), aq).copy(
|
||||
mediaAttachments = it.mapNotNull { resultRow ->
|
||||
resultRow.toMediaOrNull()?.toMediaAttachments()
|
||||
},
|
||||
emojis = it.mapNotNull { resultRow -> resultRow.toCustomEmojiOrNull()?.toMastodonEmoji() }
|
||||
) to it.first()[Posts.repostId]
|
||||
) to it.first()[aq[Posts.repostId]]
|
||||
}
|
||||
return resolveReplyAndRepost(map).singleOrNull()
|
||||
}
|
||||
|
@ -160,6 +188,7 @@ class StatusQueryServiceImpl : StatusQueryService {
|
|||
}
|
||||
|
||||
private suspend fun findByPostIdsWithMedia(ids: List<Long>): List<Status> {
|
||||
val qa = authorizedQuery()
|
||||
val pairs = Posts
|
||||
.leftJoin(PostsMedia)
|
||||
.leftJoin(PostsEmojis)
|
||||
|
@ -170,7 +199,7 @@ class StatusQueryServiceImpl : StatusQueryService {
|
|||
.groupBy { it[Posts.id] }
|
||||
.map { it.value }
|
||||
.map {
|
||||
toStatus(it.first()).copy(
|
||||
toStatus(it.first(), qa).copy(
|
||||
mediaAttachments = it.mapNotNull { resultRow ->
|
||||
resultRow.toMediaOrNull()?.toMediaAttachments()
|
||||
},
|
||||
|
@ -189,10 +218,10 @@ private fun CustomEmoji.toMastodonEmoji(): MastodonEmoji = MastodonEmoji(
|
|||
category = this.category.orEmpty()
|
||||
)
|
||||
|
||||
private fun toStatus(it: ResultRow) = Status(
|
||||
id = it[Posts.id].toString(),
|
||||
uri = it[Posts.apId],
|
||||
createdAt = it[Posts.createdAt].toString(),
|
||||
private fun toStatus(it: ResultRow, queryAlias: QueryAlias) = Status(
|
||||
id = it[queryAlias[Posts.id]].toString(),
|
||||
uri = it[queryAlias[Posts.apId]],
|
||||
createdAt = it[queryAlias[Posts.createdAt]].toString(),
|
||||
account = Account(
|
||||
id = it[Actors.id].toString(),
|
||||
username = it[Actors.name],
|
||||
|
@ -220,15 +249,15 @@ private fun toStatus(it: ResultRow) = Status(
|
|||
suspended = false,
|
||||
limited = false
|
||||
),
|
||||
content = it[Posts.text],
|
||||
visibility = when (Visibility.valueOf(it[Posts.visibility])) {
|
||||
content = it[queryAlias[Posts.text]],
|
||||
visibility = when (Visibility.valueOf(it[queryAlias[Posts.visibility]])) {
|
||||
Visibility.PUBLIC -> public
|
||||
Visibility.UNLISTED -> unlisted
|
||||
Visibility.FOLLOWERS -> private
|
||||
Visibility.DIRECT -> direct
|
||||
},
|
||||
sensitive = it[Posts.sensitive],
|
||||
spoilerText = it[Posts.overview].orEmpty(),
|
||||
sensitive = it[queryAlias[Posts.sensitive]],
|
||||
spoilerText = it[queryAlias[Posts.overview]].orEmpty(),
|
||||
mediaAttachments = emptyList(),
|
||||
mentions = emptyList(),
|
||||
tags = emptyList(),
|
||||
|
@ -236,11 +265,11 @@ private fun toStatus(it: ResultRow) = Status(
|
|||
reblogsCount = 0,
|
||||
favouritesCount = 0,
|
||||
repliesCount = 0,
|
||||
url = it[Posts.apId],
|
||||
inReplyToId = it[Posts.replyId]?.toString(),
|
||||
url = it[queryAlias[Posts.apId]],
|
||||
inReplyToId = it[queryAlias[Posts.replyId]]?.toString(),
|
||||
inReplyToAccountId = null,
|
||||
language = null,
|
||||
text = it[Posts.text],
|
||||
text = it[queryAlias[Posts.text]],
|
||||
editedAt = null
|
||||
)
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package dev.usbharu.hideout.mastodon.interfaces.api
|
||||
|
||||
import dev.usbharu.hideout.core.application.actor.GetUserDetail
|
||||
import dev.usbharu.hideout.core.application.actor.GetUserDetailApplicationService
|
||||
import dev.usbharu.hideout.core.application.relationship.acceptfollowrequest.AcceptFollowRequest
|
||||
import dev.usbharu.hideout.core.application.relationship.acceptfollowrequest.UserAcceptFollowRequestApplicationService
|
||||
|
@ -160,7 +159,7 @@ class SpringAccountApi(
|
|||
override suspend fun apiV1AccountsVerifyCredentialsGet(): ResponseEntity<CredentialAccount> {
|
||||
val principal = principalContextHolder.getPrincipal()
|
||||
val localActor =
|
||||
getUserDetailApplicationService.execute(GetUserDetail(principal.userDetailId.id), principal)
|
||||
getUserDetailApplicationService.execute(Unit, principal)
|
||||
|
||||
return ResponseEntity.ok(
|
||||
CredentialAccount(
|
||||
|
|
|
@ -54,6 +54,7 @@ class SpringStatusApi(
|
|||
|
||||
override suspend fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity<Status> {
|
||||
|
||||
val principal = principalContextHolder.getPrincipal()
|
||||
val execute = registerLocalPostApplicationService.execute(
|
||||
RegisterLocalPost(
|
||||
content = statusesRequest.status.orEmpty(),
|
||||
|
@ -69,12 +70,12 @@ class SpringStatusApi(
|
|||
replyId = statusesRequest.inReplyToId?.toLong(),
|
||||
sensitive = statusesRequest.sensitive == true,
|
||||
mediaIds = statusesRequest.mediaIds.orEmpty().map { it.toLong() }
|
||||
), principalContextHolder.getPrincipal()
|
||||
), principal
|
||||
)
|
||||
|
||||
|
||||
val status =
|
||||
getStatusApplicationService.execute(GetStatus(execute.toString()), principalContextHolder.getPrincipal())
|
||||
getStatusApplicationService.execute(GetStatus(execute.toString()), principal)
|
||||
return ResponseEntity.ok(
|
||||
status
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue