This commit is contained in:
usbharu 2024-06-02 17:49:59 +09:00
parent 922bdb4991
commit ccd089fa8e
80 changed files with 523 additions and 1155 deletions

View File

@ -17,22 +17,21 @@
package dev.usbharu.hideout.core.application.actor package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.shared.Transaction import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.Actor2Repository
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class DeleteLocalActorApplicationService( class DeleteLocalActorApplicationService(
private val transaction: Transaction, private val transaction: Transaction,
private val actor2Repository: Actor2Repository, private val actorRepository: ActorRepository,
) { ) {
suspend fun delete(actorId: Long, executor: ActorId) { suspend fun delete(actorId: Long, executor: ActorId) {
transaction.transaction { transaction.transaction {
val id = ActorId(actorId) val id = ActorId(actorId)
val findById = actor2Repository.findById(id)!! val findById = actorRepository.findById(id)!!
findById.delete() findById.delete()
actor2Repository.delete(findById) actorRepository.delete(findById)
} }
} }
} }

View File

@ -17,8 +17,8 @@
package dev.usbharu.hideout.core.application.actor package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.shared.Transaction import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.Actor2Repository
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.service.actor.local.AccountMigrationCheck.* import dev.usbharu.hideout.core.domain.service.actor.local.AccountMigrationCheck.*
import dev.usbharu.hideout.core.domain.service.actor.local.LocalActorMigrationCheckDomainService import dev.usbharu.hideout.core.domain.service.actor.local.LocalActorMigrationCheckDomainService
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -26,24 +26,23 @@ import org.springframework.stereotype.Service
@Service @Service
class MigrationLocalActorApplicationService( class MigrationLocalActorApplicationService(
private val transaction: Transaction, private val transaction: Transaction,
private val actor2Repository: Actor2Repository, private val actorRepository: ActorRepository,
private val localActorMigrationCheckDomainService: LocalActorMigrationCheckDomainService, private val localActorMigrationCheckDomainService: LocalActorMigrationCheckDomainService,
) { ) {
suspend fun migration(from: Long, to: Long, executor: ActorId) { suspend fun migration(from: Long, to: Long, executor: ActorId) {
transaction.transaction<Unit> { transaction.transaction<Unit> {
val fromActorId = ActorId(from) val fromActorId = ActorId(from)
val toActorId = ActorId(to) val toActorId = ActorId(to)
val fromActor = actor2Repository.findById(fromActorId)!! val fromActor = actorRepository.findById(fromActorId)!!
val toActor = actor2Repository.findById(toActorId)!! val toActor = actorRepository.findById(toActorId)!!
val canAccountMigration = localActorMigrationCheckDomainService.canAccountMigration(fromActor, toActor) val canAccountMigration = localActorMigrationCheckDomainService.canAccountMigration(fromActor, toActor)
when (canAccountMigration) { when (canAccountMigration) {
is AlreadyMoved -> TODO() is AlreadyMoved -> TODO()
is CanAccountMigration -> { is CanAccountMigration -> {
fromActor.moveTo = toActorId fromActor.moveTo = toActorId
actor2Repository.save(fromActor) actorRepository.save(fromActor)
} }
is CircularReferences -> TODO() is CircularReferences -> TODO()
@ -51,6 +50,5 @@ class MigrationLocalActorApplicationService(
is AlsoKnownAsNotFound -> TODO() is AlsoKnownAsNotFound -> TODO()
} }
} }
} }
} }

View File

@ -19,22 +19,22 @@ package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.application.shared.Transaction import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.Actor2Repository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.service.actor.local.LocalActorDomainService import dev.usbharu.hideout.core.domain.service.actor.local.LocalActorDomainService
import dev.usbharu.hideout.core.domain.service.userdetail.UserDetailDomainService import dev.usbharu.hideout.core.domain.service.userdetail.UserDetailDomainService
import dev.usbharu.hideout.core.infrastructure.factory.Actor2FactoryImpl import dev.usbharu.hideout.core.infrastructure.factory.ActorFactoryImpl
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class RegisterLocalActorApplicationService( class RegisterLocalActorApplicationService(
private val transaction: Transaction, private val transaction: Transaction,
private val actorDomainService: LocalActorDomainService, private val actorDomainService: LocalActorDomainService,
private val actor2Repository: Actor2Repository, private val actorRepository: ActorRepository,
private val actor2FactoryImpl: Actor2FactoryImpl, private val actorFactoryImpl: ActorFactoryImpl,
private val instanceRepository: InstanceRepository, private val instanceRepository: InstanceRepository,
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
private val userDetailDomainService: UserDetailDomainService, private val userDetailDomainService: UserDetailDomainService,
@ -44,26 +44,23 @@ class RegisterLocalActorApplicationService(
suspend fun register(registerLocalActor: RegisterLocalActor) { suspend fun register(registerLocalActor: RegisterLocalActor) {
transaction.transaction { transaction.transaction {
if (actorDomainService.usernameAlreadyUse(registerLocalActor.name)) { if (actorDomainService.usernameAlreadyUse(registerLocalActor.name)) {
//todo 適切な例外を考える // todo 適切な例外を考える
throw Exception("Username already exists") throw Exception("Username already exists")
} }
val instance = instanceRepository.findByUrl(applicationConfig.url.toURI())!! val instance = instanceRepository.findByUrl(applicationConfig.url.toURI())!!
val actor = actorFactoryImpl.createLocal(
val actor = actor2FactoryImpl.createLocal(
registerLocalActor.name, registerLocalActor.name,
actorDomainService.generateKeyPair(), actorDomainService.generateKeyPair(),
instance.id instance.id
) )
actor2Repository.save(actor) actorRepository.save(actor)
val userDetail = UserDetail.create( val userDetail = UserDetail.create(
id = UserDetailId(idGenerateService.generateId()), id = UserDetailId(idGenerateService.generateId()),
actorId = actor.id, actorId = actor.id,
password = userDetailDomainService.hashPassword(registerLocalActor.password), password = userDetailDomainService.hashPassword(registerLocalActor.password),
) )
userDetailRepository.save(userDetail) userDetailRepository.save(userDetail)
} }
} }
} }

View File

@ -17,24 +17,21 @@
package dev.usbharu.hideout.core.application.actor package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.shared.Transaction import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.Actor2Repository
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class SuspendLocalActorApplicationService( class SuspendLocalActorApplicationService(
private val transaction: Transaction, private val transaction: Transaction,
private val actor2Repository: Actor2Repository, private val actorRepository: ActorRepository,
) { ) {
suspend fun suspend(actorId: Long, executor: ActorId) { suspend fun suspend(actorId: Long, executor: ActorId) {
transaction.transaction { transaction.transaction {
val id = ActorId(actorId) val id = ActorId(actorId)
val findById = actor2Repository.findById(id)!! val findById = actorRepository.findById(id)!!
findById.suspend = true findById.suspend = true
} }
} }
} }

View File

@ -17,21 +17,20 @@
package dev.usbharu.hideout.core.application.actor package dev.usbharu.hideout.core.application.actor
import dev.usbharu.hideout.core.application.shared.Transaction import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.actor.Actor2Repository
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class UnsuspendLocalActorApplicationService( class UnsuspendLocalActorApplicationService(
private val transaction: Transaction, private val transaction: Transaction,
private val actor2Repository: Actor2Repository, private val actorRepository: ActorRepository,
) { ) {
suspend fun unsuspend(actorId: Long, executor: Long) { suspend fun unsuspend(actorId: Long, executor: Long) {
transaction.transaction { transaction.transaction {
val findById = actor2Repository.findById(ActorId(actorId))!! val findById = actorRepository.findById(ActorId(actorId))!!
findById.suspend = false findById.suspend = false
} }
} }
} }

View File

@ -16,12 +16,12 @@
package dev.usbharu.hideout.core.application.post package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.domain.model.post.Post2Repository
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class DeleteLocalPostApplicationService(private val postRepository: Post2Repository) { class DeleteLocalPostApplicationService(private val postRepository: PostRepository) {
suspend fun delete(postId: Long) { suspend fun delete(postId: Long) {
val findById = postRepository.findById(PostId(postId))!! val findById = postRepository.findById(PostId(postId))!!
findById.delete() findById.delete()

View File

@ -16,27 +16,26 @@
package dev.usbharu.hideout.core.application.post package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.domain.model.actor.Actor2Repository
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.Post2Repository
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostOverview import dev.usbharu.hideout.core.domain.model.post.PostOverview
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.infrastructure.factory.PostFactoryImpl import dev.usbharu.hideout.core.infrastructure.factory.PostFactoryImpl
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class RegisterLocalPostApplicationService( class RegisterLocalPostApplicationService(
private val postFactory: PostFactoryImpl, private val postFactory: PostFactoryImpl,
private val actor2Repository: Actor2Repository, private val actorRepository: ActorRepository,
private val postRepository: Post2Repository, private val postRepository: PostRepository,
) { ) {
suspend fun register(registerLocalPost: RegisterLocalPost) { suspend fun register(registerLocalPost: RegisterLocalPost) {
val actorId = ActorId(registerLocalPost.actorId) val actorId = ActorId(registerLocalPost.actorId)
val post = postFactory.createLocal( val post = postFactory.createLocal(
actorId, actorId,
actor2Repository.findById(actorId)!!.name, actorRepository.findById(actorId)!!.name,
PostOverview(registerLocalPost.overview), PostOverview(registerLocalPost.overview),
registerLocalPost.content, registerLocalPost.content,
registerLocalPost.visibility, registerLocalPost.visibility,

View File

@ -18,16 +18,16 @@ package dev.usbharu.hideout.core.application.post
import dev.usbharu.hideout.core.application.shared.Transaction import dev.usbharu.hideout.core.application.shared.Transaction
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.Post2Repository
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostOverview import dev.usbharu.hideout.core.domain.model.post.PostOverview
import dev.usbharu.hideout.core.domain.model.post.PostRepository
import dev.usbharu.hideout.core.infrastructure.factory.PostContentFactoryImpl import dev.usbharu.hideout.core.infrastructure.factory.PostContentFactoryImpl
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class UpdateLocalNoteApplicationService( class UpdateLocalNoteApplicationService(
private val transaction: Transaction, private val transaction: Transaction,
private val postRepository: Post2Repository, private val postRepository: PostRepository,
private val postContentFactoryImpl: PostContentFactoryImpl, private val postContentFactoryImpl: PostContentFactoryImpl,
) { ) {
suspend fun update(updateLocalNote: UpdateLocalNote) { suspend fun update(updateLocalNote: UpdateLocalNote) {

View File

@ -16,11 +16,11 @@
package dev.usbharu.hideout.core.domain.event.actor package dev.usbharu.hideout.core.domain.event.actor
import dev.usbharu.hideout.core.domain.model.actor.Actor2 import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class ActorDomainEventFactory(private val actor: Actor2) { class ActorDomainEventFactory(private val actor: Actor) {
fun createEvent(actorEvent: ActorEvent): DomainEvent { fun createEvent(actorEvent: ActorEvent): DomainEvent {
return DomainEvent.create( return DomainEvent.create(
actorEvent.eventName, actorEvent.eventName,
@ -30,7 +30,7 @@ class ActorDomainEventFactory(private val actor: Actor2) {
} }
} }
class ActorEventBody(actor: Actor2) : DomainEventBody( class ActorEventBody(actor: Actor) : DomainEventBody(
mapOf( mapOf(
"actor" to actor "actor" to actor
) )

View File

@ -16,11 +16,11 @@
package dev.usbharu.hideout.core.domain.event.post package dev.usbharu.hideout.core.domain.event.post
import dev.usbharu.hideout.core.domain.model.post.Post2 import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class PostDomainEventFactory(private val post: Post2) { class PostDomainEventFactory(private val post: Post) {
fun createEvent(postEvent: PostEvent): DomainEvent { fun createEvent(postEvent: PostEvent): DomainEvent {
return DomainEvent.create( return DomainEvent.create(
postEvent.eventName, postEvent.eventName,
@ -29,7 +29,7 @@ class PostDomainEventFactory(private val post: Post2) {
} }
} }
class PostEventBody(post: Post2) : DomainEventBody(mapOf("post" to post)) class PostEventBody(post: Post) : DomainEventBody(mapOf("post" to post))
enum class PostEvent(val eventName: String) { enum class PostEvent(val eventName: String) {
delete("PostDelete"), delete("PostDelete"),

View File

@ -16,17 +16,17 @@
package dev.usbharu.hideout.core.domain.event.relationship package dev.usbharu.hideout.core.domain.event.relationship
import dev.usbharu.hideout.core.domain.model.relationship.Relationship2 import dev.usbharu.hideout.core.domain.model.relationship.Relationship
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class RelationshipEventFactory(private val relationship2: Relationship2) { class RelationshipEventFactory(private val relationship: Relationship) {
fun createEvent(relationshipEvent: RelationshipEvent): DomainEvent { fun createEvent(relationshipEvent: RelationshipEvent): DomainEvent {
return DomainEvent.create(relationshipEvent.eventName, RelationshipEventBody(relationship2)) return DomainEvent.create(relationshipEvent.eventName, RelationshipEventBody(relationship))
} }
} }
class RelationshipEventBody(relationship: Relationship2) : DomainEventBody(mapOf("relationship" to relationship)) class RelationshipEventBody(relationship: Relationship) : DomainEventBody(mapOf("relationship" to relationship))
enum class RelationshipEvent(val eventName: String) { enum class RelationshipEvent(val eventName: String) {
follow("RelationshipFollow"), follow("RelationshipFollow"),

View File

@ -18,13 +18,14 @@ package dev.usbharu.hideout.core.domain.model.actor
import dev.usbharu.hideout.core.domain.event.actor.ActorDomainEventFactory import dev.usbharu.hideout.core.domain.event.actor.ActorDomainEventFactory
import dev.usbharu.hideout.core.domain.event.actor.ActorEvent.* import dev.usbharu.hideout.core.domain.event.actor.ActorEvent.*
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
import dev.usbharu.hideout.core.domain.model.instance.InstanceId import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.shared.Domain import dev.usbharu.hideout.core.domain.model.shared.Domain
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
import java.net.URI import java.net.URI
import java.time.Instant import java.time.Instant
class Actor2 private constructor( class Actor(
val id: ActorId, val id: ActorId,
val name: ActorName, val name: ActorName,
val domain: Domain, val domain: Domain,
@ -49,6 +50,8 @@ class Actor2 private constructor(
var lastUpdateAt: Instant = createdAt, var lastUpdateAt: Instant = createdAt,
alsoKnownAs: Set<ActorId> = emptySet(), alsoKnownAs: Set<ActorId> = emptySet(),
moveTo: ActorId? = null, moveTo: ActorId? = null,
emojiIds: Set<EmojiId>,
deleted: Boolean,
) : DomainEventStorable() { ) : DomainEventStorable() {
var suspend = suspend var suspend = suspend
@ -74,9 +77,8 @@ class Actor2 private constructor(
field = value field = value
} }
var emojis = emojiIds
val emojis private set
get() = screenName.emojis + description.emojis
var description = description var description = description
set(value) { set(value) {
@ -89,62 +91,28 @@ class Actor2 private constructor(
field = value field = value
} }
var deleted = deleted
private set
fun delete() { fun delete() {
if (deleted.not()) {
addDomainEvent(ActorDomainEventFactory(this).createEvent(delete)) addDomainEvent(ActorDomainEventFactory(this).createEvent(delete))
screenName = ActorScreenName.empty
description = ActorDescription.empty
emojis = emptySet()
lastPostAt = null
postsCount = ActorPostsCount.ZERO
followersCount = null
followingCount = null
}
}
fun restore() {
deleted = false
checkUpdate()
} }
fun checkUpdate() { fun checkUpdate() {
addDomainEvent(ActorDomainEventFactory(this).createEvent(checkUpdate)) addDomainEvent(ActorDomainEventFactory(this).createEvent(checkUpdate))
} }
abstract class Actor2Factory {
protected suspend fun internalCreate(
id: ActorId,
name: ActorName,
domain: Domain,
screenName: ActorScreenName,
description: ActorDescription,
inbox: URI,
outbox: URI,
url: URI,
publicKey: ActorPublicKey,
privateKey: ActorPrivateKey? = null,
createdAt: Instant,
keyId: ActorKeyId,
followersEndpoint: URI,
followingEndpoint: URI,
instance: InstanceId,
locked: Boolean,
followersCount: ActorRelationshipCount,
followingCount: ActorRelationshipCount,
postsCount: ActorPostsCount,
lastPostDate: Instant? = null,
suspend: Boolean,
): Actor2 {
return Actor2(
id = id,
name = name,
domain = domain,
screenName = screenName,
description = description,
inbox = inbox,
outbox = outbox,
url = url,
publicKey = publicKey,
privateKey = privateKey,
createdAt = createdAt,
keyId = keyId,
followersEndpoint = followersEndpoint,
followingEndpoint = followingEndpoint,
instance = instance,
locked = locked,
followersCount = followersCount,
followingCount = followingCount,
postsCount = postsCount,
lastPostAt = lastPostDate,
suspend = suspend
)
}
}
} }

View File

@ -16,15 +16,10 @@
package dev.usbharu.hideout.core.domain.model.actor package dev.usbharu.hideout.core.domain.model.actor
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId @JvmInline
value class ActorDescription(val description: String) {
class ActorDescription private constructor(val description: String, val emojis: List<EmojiId>) {
companion object { companion object {
val length = 10000 val length = 10000
} val empty = ActorDescription("")
abstract class ActorDescriptionFactory {
protected suspend fun create(description: String, emojis: List<EmojiId>): ActorDescription =
ActorDescription(description, emojis)
} }
} }

View File

@ -18,9 +18,6 @@ package dev.usbharu.hideout.core.domain.model.actor
@JvmInline @JvmInline
value class ActorName(val name: String) { value class ActorName(val name: String) {
init {
}
companion object { companion object {
val length = 300 val length = 300

View File

@ -24,4 +24,8 @@ value class ActorPostsCount(val postsCount: Int) {
operator fun inc() = ActorPostsCount(postsCount + 1) operator fun inc() = ActorPostsCount(postsCount + 1)
operator fun dec() = ActorPostsCount(postsCount - 1) operator fun dec() = ActorPostsCount(postsCount - 1)
companion object {
val ZERO = ActorPostsCount(0)
}
} }

View File

@ -16,8 +16,8 @@
package dev.usbharu.hideout.core.domain.model.actor package dev.usbharu.hideout.core.domain.model.actor
interface Actor2Repository { interface ActorRepository {
suspend fun save(actor: Actor2): Actor2 suspend fun save(actor: Actor): Actor
suspend fun delete(actor: Actor2) suspend fun delete(actor: Actor)
suspend fun findById(id: ActorId): Actor2? suspend fun findById(id: ActorId): Actor?
} }

View File

@ -16,16 +16,10 @@
package dev.usbharu.hideout.core.domain.model.actor package dev.usbharu.hideout.core.domain.model.actor
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId @JvmInline
value class ActorScreenName(val screenName: String) {
class ActorScreenName private constructor(val screenName: String, val emojis: List<EmojiId>) {
companion object { companion object {
val length = 300 val length = 300
} val empty = ActorScreenName("")
abstract class ActorScreenNameFactory {
protected suspend fun create(screenName: String, emojis: List<EmojiId>): ActorScreenName =
ActorScreenName(screenName, emojis)
} }
} }

View File

@ -1,32 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.model.deletedActor
import dev.usbharu.hideout.core.domain.model.actor.ActorName
import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey
import dev.usbharu.hideout.core.domain.model.shared.Domain
import java.net.URI
import java.time.Instant
data class DeletedActor(
val id: DeletedActorId,
val name: ActorName,
val domain: Domain,
val apId: URI,
val publicKey: ActorPublicKey,
val deletedAt: Instant,
)

View File

@ -1,20 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.model.deletedActor
@JvmInline
value class DeletedActorId(val id: Long)

View File

@ -1,24 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.model.deletedActor
interface DeletedActorRepository {
suspend fun save(deletedActor: DeletedActor): DeletedActor
suspend fun delete(deletedActor: DeletedActor)
suspend fun findById(id: DeletedActorId): DeletedActor?
suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor?
}

View File

@ -17,10 +17,8 @@
package dev.usbharu.hideout.core.domain.model.emoji package dev.usbharu.hideout.core.domain.model.emoji
interface CustomEmojiRepository { interface CustomEmojiRepository {
suspend fun generateId(): Long
suspend fun save(customEmoji: CustomEmoji): CustomEmoji suspend fun save(customEmoji: CustomEmoji): CustomEmoji
suspend fun findById(id: Long): CustomEmoji? suspend fun findById(id: Long): CustomEmoji?
suspend fun delete(customEmoji: CustomEmoji) suspend fun delete(customEmoji: CustomEmoji)
suspend fun findByNameAndDomain(name: String, domain: String): CustomEmoji?
suspend fun findByNamesAndDomain(names: List<String>, domain: String): List<CustomEmoji> suspend fun findByNamesAndDomain(names: List<String>, domain: String): List<CustomEmoji>
} }

View File

@ -37,7 +37,6 @@ class Instance(
val createdAt: Instant, val createdAt: Instant,
) : DomainEventStorable() { ) : DomainEventStorable() {
var iconUrl = iconUrl var iconUrl = iconUrl
set(value) { set(value) {
addDomainEvent(InstanceEventFactory(this).createEvent(InstanceEvent.update)) addDomainEvent(InstanceEventFactory(this).createEvent(InstanceEvent.update))
@ -56,6 +55,4 @@ class Instance(
override fun hashCode(): Int { override fun hashCode(): Int {
return id.hashCode() return id.hashCode()
} }
} }

View File

@ -19,7 +19,6 @@ package dev.usbharu.hideout.core.domain.model.instance
import java.net.URI import java.net.URI
interface InstanceRepository { interface InstanceRepository {
suspend fun generateId(): InstanceId
suspend fun save(instance: Instance): Instance suspend fun save(instance: Instance): Instance
suspend fun findById(id: InstanceId): Instance? suspend fun findById(id: InstanceId): Instance?
suspend fun delete(instance: Instance) suspend fun delete(instance: Instance)

View File

@ -29,4 +29,3 @@ data class Media(
val blurHash: MediaBlurHash?, val blurHash: MediaBlurHash?,
val description: MediaDescription? = null, val description: MediaDescription? = null,
) )

View File

@ -17,9 +17,7 @@
package dev.usbharu.hideout.core.domain.model.media package dev.usbharu.hideout.core.domain.model.media
interface MediaRepository { interface MediaRepository {
suspend fun generateId(): Long
suspend fun save(media: Media): Media suspend fun save(media: Media): Media
suspend fun findById(id: Long): Media? suspend fun findById(id: MediaId): Media?
suspend fun delete(id: Long) suspend fun delete(media: Media)
suspend fun findByRemoteUrl(remoteUrl: String): Media?
} }

View File

@ -24,7 +24,7 @@ import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
import java.net.URI import java.net.URI
import java.time.Instant import java.time.Instant
class Post2 private constructor( class Post private constructor(
val id: PostId, val id: PostId,
actorId: ActorId, actorId: ActorId,
overview: PostOverview? = null, overview: PostOverview? = null,
@ -143,7 +143,6 @@ class Post2 private constructor(
content = PostContent.empty content = PostContent.empty
overview = null overview = null
mediaIds = emptyList() mediaIds = emptyList()
} }
deleted = true deleted = true
} }
@ -157,6 +156,7 @@ class Post2 private constructor(
this.content = content this.content = content
this.overview = overview this.overview = overview
this.mediaIds = mediaIds this.mediaIds = mediaIds
checkUpdate()
} }
var hide = hide var hide = hide
@ -182,7 +182,7 @@ class Post2 private constructor(
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Post2 other as Post
return id == other.id return id == other.id
} }
@ -207,8 +207,8 @@ class Post2 private constructor(
deleted: Boolean, deleted: Boolean,
mediaIds: List<MediaId>, mediaIds: List<MediaId>,
hide: Boolean, hide: Boolean,
): Post2 { ): Post {
return Post2( return Post(
id = id, id = id,
actorId = actorId, actorId = actorId,
overview = overview, overview = overview,

View File

@ -29,7 +29,9 @@ class PostContent private constructor(val text: String, val content: String, val
abstract class PostContentFactory { abstract class PostContentFactory {
protected suspend fun create(text: String, content: String, emojiIds: List<EmojiId>): PostContent { protected suspend fun create(text: String, content: String, emojiIds: List<EmojiId>): PostContent {
return PostContent( return PostContent(
text, content, emojiIds text,
content,
emojiIds
) )
} }
} }

View File

@ -18,10 +18,10 @@ package dev.usbharu.hideout.core.domain.model.post
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
interface Post2Repository { interface PostRepository {
suspend fun save(post: Post2): Post2 suspend fun save(post: Post): Post
suspend fun saveAll(posts: List<Post2>): List<Post2> suspend fun saveAll(posts: List<Post>): List<Post>
suspend fun findById(id: PostId): Post2? suspend fun findById(id: PostId): Post?
suspend fun findByActorId(id: ActorId): List<Post2> suspend fun findByActorId(id: ActorId): List<Post>
suspend fun delete(post: Post2) suspend fun delete(post: Post)
} }

View File

@ -21,7 +21,7 @@ import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventFacto
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
class Relationship2( class Relationship(
val actorId: ActorId, val actorId: ActorId,
val targetActorId: ActorId, val targetActorId: ActorId,
following: Boolean, following: Boolean,
@ -43,7 +43,6 @@ class Relationship2(
var mutingFollowRequest: Boolean = mutingFollowRequest var mutingFollowRequest: Boolean = mutingFollowRequest
private set private set
fun follow() { fun follow() {
require(blocking.not()) require(blocking.not())
following = true following = true

View File

@ -16,7 +16,7 @@
package dev.usbharu.hideout.core.domain.model.relationship package dev.usbharu.hideout.core.domain.model.relationship
interface Relationship2Repository { interface RelationshipRepository {
suspend fun save(relationship2: Relationship2): Relationship2 suspend fun save(relationship: Relationship): Relationship
suspend fun delete(relationship2: Relationship2) suspend fun delete(relationship: Relationship)
} }

View File

@ -17,14 +17,14 @@
package dev.usbharu.hideout.core.domain.service.actor package dev.usbharu.hideout.core.domain.service.actor
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.model.actor.Actor2 import dev.usbharu.hideout.core.domain.model.actor.Actor
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
interface IRemoteActorCheckDomainService { interface IRemoteActorCheckDomainService {
fun isRemoteActor(actor: Actor2): Boolean fun isRemoteActor(actor: Actor): Boolean
} }
@Service @Service
class RemoteActorCheckDomainService(private val applicationConfig: ApplicationConfig) : IRemoteActorCheckDomainService { class RemoteActorCheckDomainService(private val applicationConfig: ApplicationConfig) : IRemoteActorCheckDomainService {
override fun isRemoteActor(actor: Actor2): Boolean = actor.domain.domain == applicationConfig.url.host override fun isRemoteActor(actor: Actor): Boolean = actor.domain.domain == applicationConfig.url.host
} }

View File

@ -16,10 +16,10 @@
package dev.usbharu.hideout.core.domain.service.actor.local package dev.usbharu.hideout.core.domain.service.actor.local
import dev.usbharu.hideout.core.domain.model.actor.Actor2 import dev.usbharu.hideout.core.domain.model.actor.Actor
interface LocalActorMigrationCheckDomainService { interface LocalActorMigrationCheckDomainService {
suspend fun canAccountMigration(from: Actor2, to: Actor2): AccountMigrationCheck suspend fun canAccountMigration(from: Actor, to: Actor): AccountMigrationCheck
} }
sealed class AccountMigrationCheck( sealed class AccountMigrationCheck(

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.exposed
import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
import dev.usbharu.hideout.core.infrastructure.exposedrepository.ActorsAlsoKnownAs
import org.jetbrains.exposed.sql.Query
import org.jetbrains.exposed.sql.ResultRow
import org.springframework.stereotype.Component
@Component
class ActorQueryMapper(private val actorResultRowMapper: ResultRowMapper<Actor>) : QueryMapper<Actor> {
override fun map(query: Query): List<Actor> {
return query
.groupBy { it[Actors.id] }
.map { it.value }
.map {
it
.first()
.let(actorResultRowMapper::map)
.apply {
alsoKnownAs = it.mapNotNull { resultRow: ResultRow ->
resultRow.getOrNull(
ActorsAlsoKnownAs.alsoKnownAs
)?.let { actorId ->
ActorId(
actorId
)
}
}.toSet()
clearDomainEvents()
}
}
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.exposed
import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper
import dev.usbharu.hideout.core.domain.model.actor.*
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.shared.Domain
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors
import org.jetbrains.exposed.sql.ResultRow
import org.springframework.stereotype.Component
import java.net.URI
@Component
class ActorResultRowMapper : ResultRowMapper<Actor> {
override fun map(resultRow: ResultRow): Actor {
return Actor(
id = ActorId(resultRow[Actors.id]),
name = ActorName(resultRow[Actors.name]),
domain = Domain(resultRow[Actors.domain]),
screenName = ActorScreenName(resultRow[Actors.screenName]),
description = ActorDescription(resultRow[Actors.description]),
inbox = URI.create(resultRow[Actors.inbox]),
outbox = URI.create(resultRow[Actors.outbox]),
url = URI.create(resultRow[Actors.url]),
publicKey = ActorPublicKey(resultRow[Actors.publicKey]),
privateKey = resultRow[Actors.privateKey]?.let { ActorPrivateKey(it) },
createdAt = resultRow[Actors.createdAt],
keyId = ActorKeyId(resultRow[Actors.keyId]),
followersEndpoint = resultRow[Actors.followers]?.let { URI.create(it) },
followingEndpoint = resultRow[Actors.following]?.let { URI.create(it) },
instance = InstanceId(resultRow[Actors.instance]),
locked = resultRow[Actors.locked],
followersCount = resultRow[Actors.followersCount]?.let { ActorRelationshipCount(it) },
followingCount = resultRow[Actors.followingCount]?.let { ActorRelationshipCount(it) },
postsCount = ActorPostsCount(resultRow[Actors.postsCount]),
lastPostAt = resultRow[Actors.lastPostAt],
suspend = resultRow[Actors.suspend],
lastUpdateAt = resultRow[Actors.lastUpdateAt],
alsoKnownAs = emptySet(),
moveTo = resultRow[Actors.moveTo]?.let { ActorId(it) },
emojiIds = resultRow[Actors.emojis]
.split(",")
.filter { it.isNotEmpty() }
.map { EmojiId(it.toLong()) }
.toSet(),
deleted = resultRow[Actors.deleted]
)
}
}

View File

@ -16,9 +16,11 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.shared.Domain
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.CurrentTimestamp import org.jetbrains.exposed.sql.javatime.CurrentTimestamp
@ -26,34 +28,33 @@ import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.net.URI
@Repository @Repository
class CustomEmojiRepositoryImpl(private val idGenerateService: IdGenerateService) : CustomEmojiRepository, class CustomEmojiRepositoryImpl : CustomEmojiRepository,
AbstractRepository() { AbstractRepository() {
override val logger: Logger override val logger: Logger
get() = Companion.logger get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(customEmoji: CustomEmoji): CustomEmoji = query { override suspend fun save(customEmoji: CustomEmoji): CustomEmoji = query {
val singleOrNull = val singleOrNull =
CustomEmojis.selectAll().where { CustomEmojis.id eq customEmoji.id }.forUpdate().singleOrNull() CustomEmojis.selectAll().where { CustomEmojis.id eq customEmoji.id.emojiId }.forUpdate().singleOrNull()
if (singleOrNull == null) { if (singleOrNull == null) {
CustomEmojis.insert { CustomEmojis.insert {
it[id] = customEmoji.id it[id] = customEmoji.id.emojiId
it[name] = customEmoji.name it[name] = customEmoji.name
it[domain] = customEmoji.domain it[domain] = customEmoji.domain.domain
it[instanceId] = customEmoji.instanceId it[instanceId] = customEmoji.instanceId.instanceId
it[url] = customEmoji.url it[url] = customEmoji.url.toString()
it[category] = customEmoji.category it[category] = customEmoji.category
it[createdAt] = customEmoji.createdAt it[createdAt] = customEmoji.createdAt
} }
} else { } else {
CustomEmojis.update({ CustomEmojis.id eq customEmoji.id }) { CustomEmojis.update({ CustomEmojis.id eq customEmoji.id.emojiId }) {
it[name] = customEmoji.name it[name] = customEmoji.name
it[domain] = customEmoji.domain it[domain] = customEmoji.domain.domain
it[instanceId] = customEmoji.instanceId it[instanceId] = customEmoji.instanceId.instanceId
it[url] = customEmoji.url it[url] = customEmoji.url.toString()
it[category] = customEmoji.category it[category] = customEmoji.category
it[createdAt] = customEmoji.createdAt it[createdAt] = customEmoji.createdAt
} }
@ -66,14 +67,16 @@ class CustomEmojiRepositoryImpl(private val idGenerateService: IdGenerateService
} }
override suspend fun delete(customEmoji: CustomEmoji): Unit = query { override suspend fun delete(customEmoji: CustomEmoji): Unit = query {
CustomEmojis.deleteWhere { id eq customEmoji.id } CustomEmojis.deleteWhere { id eq customEmoji.id.emojiId }
} }
override suspend fun findByNameAndDomain(name: String, domain: String): CustomEmoji? = query { override suspend fun findByNamesAndDomain(names: List<String>, domain: String): List<CustomEmoji> {
return@query CustomEmojis return CustomEmojis
.selectAll().where { CustomEmojis.name eq name and (CustomEmojis.domain eq domain) } .selectAll()
.singleOrNull() .where {
?.toCustomEmoji() CustomEmojis.name inList names and (CustomEmojis.domain eq domain)
}
.map { it.toCustomEmoji() }
} }
companion object { companion object {
@ -82,22 +85,22 @@ class CustomEmojiRepositoryImpl(private val idGenerateService: IdGenerateService
} }
fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji( fun ResultRow.toCustomEmoji(): CustomEmoji = CustomEmoji(
id = this[CustomEmojis.id], id = EmojiId(this[CustomEmojis.id]),
name = this[CustomEmojis.name], name = this[CustomEmojis.name],
domain = this[CustomEmojis.domain], domain = Domain(this[CustomEmojis.domain]),
instanceId = this[CustomEmojis.instanceId], instanceId = InstanceId(this[CustomEmojis.instanceId]),
url = this[CustomEmojis.url], url = URI.create(this[CustomEmojis.url]),
category = this[CustomEmojis.category], category = this[CustomEmojis.category],
createdAt = this[CustomEmojis.createdAt] createdAt = this[CustomEmojis.createdAt]
) )
fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? { fun ResultRow.toCustomEmojiOrNull(): CustomEmoji? {
return CustomEmoji( return CustomEmoji(
id = this.getOrNull(CustomEmojis.id) ?: return null, id = EmojiId(this.getOrNull(CustomEmojis.id) ?: return null),
name = this.getOrNull(CustomEmojis.name) ?: return null, name = this.getOrNull(CustomEmojis.name) ?: return null,
domain = this.getOrNull(CustomEmojis.domain) ?: return null, domain = Domain(this.getOrNull(CustomEmojis.domain) ?: return null),
instanceId = this[CustomEmojis.instanceId], instanceId = InstanceId(this.getOrNull(CustomEmojis.instanceId) ?: return null),
url = this.getOrNull(CustomEmojis.url) ?: return null, url = URI.create(this.getOrNull(CustomEmojis.url) ?: return null),
category = this[CustomEmojis.category], category = this[CustomEmojis.category],
createdAt = this.getOrNull(CustomEmojis.createdAt) ?: return null createdAt = this.getOrNull(CustomEmojis.createdAt) ?: return null
) )
@ -107,7 +110,7 @@ object CustomEmojis : Table("emojis") {
val id = long("id") val id = long("id")
val name = varchar("name", 1000) val name = varchar("name", 1000)
val domain = varchar("domain", 1000) val domain = varchar("domain", 1000)
val instanceId = long("instance_id").references(Instance.id).nullable() val instanceId = long("instance_id").references(Instance.id)
val url = varchar("url", 255).uniqueIndex() val url = varchar("url", 255).uniqueIndex()
val category = varchar("category", 255).nullable() val category = varchar("category", 255).nullable()
val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp) val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)

View File

@ -1,106 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository
@Repository
class DeletedActorRepositoryImpl : DeletedActorRepository, AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun save(deletedActor: DeletedActor): DeletedActor = query {
val singleOrNull =
DeletedActors.selectAll().where { DeletedActors.id eq deletedActor.id }.forUpdate().singleOrNull()
if (singleOrNull == null) {
DeletedActors.insert {
it[id] = deletedActor.id
it[name] = deletedActor.name
it[domain] = deletedActor.domain
it[apId] = deletedActor.apId
it[publicKey] = deletedActor.publicKey
it[deletedAt] = deletedActor.deletedAt
}
} else {
DeletedActors.update({ DeletedActors.id eq deletedActor.id }) {
it[name] = deletedActor.name
it[domain] = deletedActor.domain
it[apId] = deletedActor.apId
it[publicKey] = deletedActor.publicKey
it[deletedAt] = deletedActor.deletedAt
}
}
return@query deletedActor
}
override suspend fun delete(deletedActor: DeletedActor): Unit = query {
DeletedActors.deleteWhere { id eq deletedActor.id }
}
override suspend fun findById(id: Long): DeletedActor? = query {
return@query DeletedActors
.selectAll().where { DeletedActors.id eq id }
.singleOrNull()
?.toDeletedActor()
}
override suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor? = query {
return@query DeletedActors
.selectAll().where { DeletedActors.name eq name and (DeletedActors.domain eq domain) }
.singleOrNull()
?.toDeletedActor()
}
companion object {
private val logger = LoggerFactory.getLogger(DeletedActorRepositoryImpl::class.java)
}
}
fun ResultRow.toDeletedActor(): DeletedActor = deletedActor(this)
private fun deletedActor(singleOr: ResultRow): DeletedActor {
return DeletedActor(
id = singleOr[DeletedActors.id],
name = singleOr[DeletedActors.name],
domain = singleOr[DeletedActors.domain],
apId = singleOr[DeletedActors.publicKey],
publicKey = singleOr[DeletedActors.apId],
deletedAt = singleOr[DeletedActors.deletedAt]
)
}
object DeletedActors : Table("deleted_actors") {
val id = long("id")
val name = varchar("name", 300)
val domain = varchar("domain", 255)
val apId = varchar("ap_id", 255).uniqueIndex()
val publicKey = varchar("public_key", 10000).uniqueIndex()
val deletedAt = timestamp("deleted_at")
override val primaryKey: PrimaryKey = PrimaryKey(id)
init {
uniqueIndex(name, domain)
}
}

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.infrastructure.exposed.QueryMapper
import dev.usbharu.hideout.core.domain.model.actor.* import dev.usbharu.hideout.core.domain.model.actor.*
import dev.usbharu.hideout.core.domain.model.shared.Domain import dev.usbharu.hideout.core.domain.model.shared.Domain
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
@ -12,18 +13,22 @@ import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
class ExposedActor2Repository(override val domainEventPublisher: DomainEventPublisher) : AbstractRepository(), class ExposedActorRepository(
DomainEventPublishableRepository<Actor2>, Actor2Repository { private val actorQueryMapper: QueryMapper<Actor>,
override val domainEventPublisher: DomainEventPublisher,
) : AbstractRepository(),
DomainEventPublishableRepository<Actor>,
ActorRepository {
override val logger: Logger override val logger: Logger
get() = Companion.logger get() = Companion.logger
companion object { companion object {
private val logger = LoggerFactory.getLogger(ExposedActor2Repository::class.java) private val logger = LoggerFactory.getLogger(ExposedActorRepository::class.java)
} }
override suspend fun save(actor: Actor2): Actor2 { override suspend fun save(actor: Actor): Actor {
query { query {
Actors2.upsert { Actors.upsert {
it[id] = actor.id.id it[id] = actor.id.id
it[name] = actor.name.name it[name] = actor.name.name
it[domain] = actor.domain.domain it[domain] = actor.domain.domain
@ -49,32 +54,41 @@ class ExposedActor2Repository(override val domainEventPublisher: DomainEventPubl
it[moveTo] = actor.moveTo?.id it[moveTo] = actor.moveTo?.id
it[emojis] = actor.emojis.joinToString(",") it[emojis] = actor.emojis.joinToString(",")
} }
Actors2AlsoKnownAs.deleteWhere { ActorsAlsoKnownAs.deleteWhere {
actorId eq actor.id.id actorId eq actor.id.id
} }
Actors2AlsoKnownAs.batchInsert(actor.alsoKnownAs) { ActorsAlsoKnownAs.batchInsert(actor.alsoKnownAs) {
this[Actors2AlsoKnownAs.actorId] = actor.id.id this[ActorsAlsoKnownAs.actorId] = actor.id.id
this[Actors2AlsoKnownAs.alsoKnownAs] = it.id this[ActorsAlsoKnownAs.alsoKnownAs] = it.id
} }
} }
update(actor) update(actor)
return actor return actor
} }
override suspend fun delete(actor: Actor2) { override suspend fun delete(actor: Actor) {
query { query {
Actors2.deleteWhere { id eq actor.id.id } Actors.deleteWhere { id eq actor.id.id }
Actors2AlsoKnownAs.deleteWhere { actorId eq actor.id.id } ActorsAlsoKnownAs.deleteWhere { actorId eq actor.id.id }
} }
update(actor) update(actor)
} }
override suspend fun findById(id: ActorId): Actor2? { override suspend fun findById(id: ActorId): Actor? {
TODO() return query {
Actors
.leftJoin(ActorsAlsoKnownAs, onColumn = { Actors.id }, otherColumn = { actorId })
.selectAll()
.where {
Actors.id eq id.id
}
.let(actorQueryMapper::map)
.first()
}
} }
} }
object Actors2 : Table("actors") { object Actors : Table("actors") {
val id = long("id") val id = long("id")
val name = varchar("name", ActorName.length) val name = varchar("name", ActorName.length)
val domain = varchar("domain", Domain.length) val domain = varchar("domain", Domain.length)
@ -99,6 +113,7 @@ object Actors2 : Table("actors") {
val suspend = bool("suspend") val suspend = bool("suspend")
val moveTo = long("move_to").references(id).nullable() val moveTo = long("move_to").references(id).nullable()
val emojis = varchar("emojis", 3000) val emojis = varchar("emojis", 3000)
val deleted = bool("deleted")
override val primaryKey = PrimaryKey(id) override val primaryKey = PrimaryKey(id)
@ -107,10 +122,10 @@ object Actors2 : Table("actors") {
} }
} }
object Actors2AlsoKnownAs : Table("actor_alsoknwonas") { object ActorsAlsoKnownAs : Table("actor_alsoknwonas") {
val actorId = val actorId =
long("actor_id").references(Actors2.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE) long("actor_id").references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
val alsoKnownAs = long("alsoKnownAs").references(Actors2.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE) val alsoKnownAs = long("alsoKnownAs").references(Actors.id, ReferenceOption.CASCADE, ReferenceOption.CASCADE)
override val primaryKey: PrimaryKey = PrimaryKey(actorId, alsoKnownAs) override val primaryKey: PrimaryKey = PrimaryKey(actorId, alsoKnownAs)
} }

View File

@ -1,98 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.domain.model.filter.FilterMode
import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeyword
import dev.usbharu.hideout.core.domain.model.filterkeyword.FilterKeywordRepository
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository
@Repository
class ExposedFilterKeywordRepository(private val idGenerateService: IdGenerateService) : FilterKeywordRepository,
AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(filterKeyword: FilterKeyword): FilterKeyword = query {
val empty = FilterKeywords.selectAll().where { FilterKeywords.id eq filterKeyword.id }.empty()
if (empty) {
FilterKeywords.insert {
it[id] = filterKeyword.id
it[filterId] = filterKeyword.filterId
it[keyword] = filterKeyword.keyword
it[mode] = filterKeyword.mode.name
}
} else {
FilterKeywords.update({ FilterKeywords.id eq filterKeyword.id }) {
it[filterId] = filterKeyword.filterId
it[keyword] = filterKeyword.keyword
it[mode] = filterKeyword.mode.name
}
}
filterKeyword
}
override suspend fun saveAll(filterKeywordList: List<FilterKeyword>): Unit = query {
FilterKeywords.batchInsert(filterKeywordList, ignore = true) {
this[FilterKeywords.id] = it.id
this[FilterKeywords.filterId] = it.filterId
this[FilterKeywords.keyword] = it.keyword
this[FilterKeywords.mode] = it.mode.name
}
}
override suspend fun findById(id: Long): FilterKeyword? = query {
return@query FilterKeywords.selectAll().where { FilterKeywords.id eq id }.singleOrNull()?.toFilterKeyword()
}
override suspend fun deleteById(id: Long): Unit = query {
FilterKeywords.deleteWhere { FilterKeywords.id eq id }
}
override suspend fun deleteByFilterId(filterId: Long): Unit = query {
FilterKeywords.deleteWhere { FilterKeywords.filterId eq filterId }
}
companion object {
private val logger = LoggerFactory.getLogger(ExposedFilterKeywordRepository::class.java)
}
}
fun ResultRow.toFilterKeyword(): FilterKeyword {
return FilterKeyword(
this[FilterKeywords.id],
this[FilterKeywords.filterId],
this[FilterKeywords.keyword],
this[FilterKeywords.mode].let { FilterMode.valueOf(it) }
)
}
object FilterKeywords : Table("filter_keywords") {
val id = long("id")
val filterId = long("filter_id").references(Filters.id)
val keyword = varchar("keyword", 1000)
val mode = varchar("mode", 100)
override val primaryKey: PrimaryKey = PrimaryKey(id)
}

View File

@ -1,104 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.domain.model.filter.Filter
import dev.usbharu.hideout.core.domain.model.filter.FilterAction
import dev.usbharu.hideout.core.domain.model.filter.FilterRepository
import dev.usbharu.hideout.core.domain.model.filter.FilterType
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository
@Repository
class ExposedFilterRepository(private val idGenerateService: IdGenerateService) : FilterRepository,
AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(filter: Filter): Filter = query {
val empty = Filters.selectAll().where {
Filters.id eq filter.id
}.forUpdate().empty()
if (empty) {
Filters.insert {
it[id] = filter.id
it[userId] = filter.userId
it[name] = filter.name
it[context] = filter.context.joinToString(",") { filterType -> filterType.name }
it[filterAction] = filter.filterAction.name
}
} else {
Filters.update({ Filters.id eq filter.id }) {
it[userId] = filter.userId
it[name] = filter.name
it[context] = filter.context.joinToString(",") { filterType -> filterType.name }
it[filterAction] = filter.filterAction.name
}
}
filter
}
override suspend fun findById(id: Long): Filter? = query {
return@query Filters.selectAll().where { Filters.id eq id }.singleOrNull()?.toFilter()
}
override suspend fun findByUserIdAndId(userId: Long, id: Long): Filter? = query {
return@query Filters.selectAll().where { Filters.userId eq userId and (Filters.id eq id) }.singleOrNull()
?.toFilter()
}
override suspend fun findByUserIdAndType(userId: Long, types: List<FilterType>): List<Filter> = query {
return@query Filters.selectAll().where { Filters.userId eq userId }.map { it.toFilter() }
.filter { it.context.containsAll(types) }
}
override suspend fun deleteById(id: Long): Unit = query {
Filters.deleteWhere { Filters.id eq id }
}
override suspend fun deleteByUserIdAndId(userId: Long, id: Long) {
Filters.deleteWhere { Filters.userId eq userId and (Filters.id eq id) }
}
companion object {
private val logger = LoggerFactory.getLogger(ExposedFilterRepository::class.java)
}
}
fun ResultRow.toFilter(): Filter = Filter(
this[Filters.id],
this[Filters.userId],
this[Filters.name],
this[Filters.context].split(",").filterNot(String::isEmpty).map { FilterType.valueOf(it) },
this[Filters.filterAction].let { FilterAction.valueOf(it) }
)
object Filters : Table() {
val id = long("id")
val userId = long("user_id").references(Actors.id)
val name = varchar("name", 255)
val context = varchar("context", 500)
val filterAction = varchar("action", 255)
override val primaryKey: PrimaryKey = PrimaryKey(id)
}

View File

@ -20,34 +20,37 @@ import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.post.* import dev.usbharu.hideout.core.domain.model.post.*
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.actorId import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.actorId
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.apId import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.apId
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.content import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.content
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.createdAt import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.createdAt
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.deleted import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.deleted
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.hide import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.hide
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.id import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.id
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.moveTo import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.moveTo
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.overview import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.overview
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.replyId import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.replyId
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.repostId import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.repostId
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.sensitive import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.sensitive
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.text import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.text
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.url import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.url
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts2.visibility import dev.usbharu.hideout.core.infrastructure.exposedrepository.Posts.visibility
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
import org.jetbrains.exposed.sql.javatime.timestamp import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@Repository @Repository
class ExposedPost2Repository(override val domainEventPublisher: DomainEventPublisher) : Post2Repository, class ExposedPostRepository(override val domainEventPublisher: DomainEventPublisher) :
AbstractRepository(), DomainEventPublishableRepository<Post2> { PostRepository,
override suspend fun save(post: Post2): Post2 { AbstractRepository(),
DomainEventPublishableRepository<Post> {
override suspend fun save(post: Post): Post {
query { query {
Posts2.upsert { Posts.upsert {
it[id] = post.id.id it[id] = post.id.id
it[actorId] = post.actorId.id it[actorId] = post.actorId.id
it[overview] = post.overview?.overview it[overview] = post.overview?.overview
@ -70,6 +73,9 @@ class ExposedPost2Repository(override val domainEventPublisher: DomainEventPubli
PostsEmojis.deleteWhere { PostsEmojis.deleteWhere {
postId eq post.id.id postId eq post.id.id
} }
PostsVisibleActors.deleteWhere {
postId eq post.id.id
}
PostsMedia.batchInsert(post.mediaIds) { PostsMedia.batchInsert(post.mediaIds) {
this[PostsMedia.postId] = post.id.id this[PostsMedia.postId] = post.id.id
this[PostsMedia.mediaId] = it.id this[PostsMedia.mediaId] = it.id
@ -78,14 +84,18 @@ class ExposedPost2Repository(override val domainEventPublisher: DomainEventPubli
this[PostsEmojis.postId] = post.id.id this[PostsEmojis.postId] = post.id.id
this[PostsEmojis.emojiId] = it.emojiId this[PostsEmojis.emojiId] = it.emojiId
} }
PostsVisibleActors.batchInsert(post.visibleActors) {
this[PostsVisibleActors.postId] = post.id.id
this[PostsVisibleActors.actorId] = it.id
}
} }
update(post) update(post)
return post return post
} }
override suspend fun saveAll(posts: List<Post2>): List<Post2> { override suspend fun saveAll(posts: List<Post>): List<Post> {
query { query {
Posts2.batchUpsert(posts, id) { Posts.batchUpsert(posts, id) {
this[id] = it.id.id this[id] = it.id.id
this[actorId] = it.actorId.id this[actorId] = it.actorId.id
this[overview] = it.overview?.overview this[overview] = it.overview?.overview
@ -103,15 +113,30 @@ class ExposedPost2Repository(override val domainEventPublisher: DomainEventPubli
this[moveTo] = it.moveTo?.id this[moveTo] = it.moveTo?.id
} }
val mediaIds = posts.flatMap { post -> post.mediaIds.map { post.id.id to it.id } } val mediaIds = posts.flatMap { post -> post.mediaIds.map { post.id.id to it.id } }
PostsMedia.batchUpsert(mediaIds, PostsMedia.postId) { val postsIds = posts.map { it.id.id }
PostsMedia.deleteWhere {
postId inList postsIds
}
PostsMedia.batchInsert(mediaIds) {
this[PostsMedia.postId] = it.first this[PostsMedia.postId] = it.first
this[PostsMedia.mediaId] = it.second this[PostsMedia.mediaId] = it.second
} }
val emojiIds = posts.flatMap { post -> post.emojiIds.map { post.id.id to it.emojiId } } val emojiIds = posts.flatMap { post -> post.emojiIds.map { post.id.id to it.emojiId } }
PostsEmojis.batchUpsert(emojiIds, PostsEmojis.postId) { PostsEmojis.deleteWhere {
postId inList postsIds
}
PostsEmojis.batchInsert(emojiIds) {
this[PostsEmojis.postId] = it.first this[PostsEmojis.postId] = it.first
this[PostsEmojis.emojiId] = it.second this[PostsEmojis.emojiId] = it.second
} }
val visibleActors = posts.flatMap { post -> post.visibleActors.map { post.id.id to it.id } }
PostsVisibleActors.deleteWhere {
postId inList postsIds
}
PostsVisibleActors.batchInsert(visibleActors) {
this[PostsVisibleActors.postId] = it.first
this[PostsVisibleActors.actorId] = it.second
}
} }
posts.forEach { posts.forEach {
update(it) update(it)
@ -119,17 +144,17 @@ class ExposedPost2Repository(override val domainEventPublisher: DomainEventPubli
return posts return posts
} }
override suspend fun findById(id: PostId): Post2? { override suspend fun findById(id: PostId): Post? {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun findByActorId(id: ActorId): List<Post2> { override suspend fun findByActorId(id: ActorId): List<Post> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun delete(post: Post2) { override suspend fun delete(post: Post) {
query { query {
Posts2.deleteWhere { Posts.deleteWhere {
id eq post.id.id id eq post.id.id
} }
} }
@ -139,13 +164,13 @@ class ExposedPost2Repository(override val domainEventPublisher: DomainEventPubli
override val logger: Logger = Companion.logger override val logger: Logger = Companion.logger
companion object { companion object {
private val logger = LoggerFactory.getLogger(ExposedPost2Repository::class.java) private val logger = LoggerFactory.getLogger(ExposedPostRepository::class.java)
} }
} }
object Posts2 : Table("posts") { object Posts : Table("posts") {
val id = long("id") val id = long("id")
val actorId = long("actor_id").references(Actors2.id) val actorId = long("actor_id").references(Actors.id)
val overview = varchar("overview", PostOverview.length).nullable() val overview = varchar("overview", PostOverview.length).nullable()
val content = varchar("content", PostContent.contentLength) val content = varchar("content", PostContent.contentLength)
val text = varchar("text", PostContent.textLength) val text = varchar("text", PostContent.textLength)
@ -159,7 +184,6 @@ object Posts2 : Table("posts") {
val deleted = bool("deleted") val deleted = bool("deleted")
val hide = bool("hide") val hide = bool("hide")
val moveTo = long("move_to").references(id).nullable() val moveTo = long("move_to").references(id).nullable()
} }
object PostsMedia : Table("posts_media") { object PostsMedia : Table("posts_media") {
@ -173,3 +197,8 @@ object PostsEmojis : Table("posts_emojis") {
val emojiId = long("emoji_id").references(CustomEmojis.id) val emojiId = long("emoji_id").references(CustomEmojis.id)
override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId) override val primaryKey: PrimaryKey = PrimaryKey(postId, emojiId)
} }
object PostsVisibleActors : Table("posts_visible_actors") {
val postId = long("post_id").references(id)
val actorId = long("actor_id").references(Actors.id)
}

View File

@ -1,150 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.domain.model.post.Visibility
import org.jetbrains.exposed.sql.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Repository
@Repository
@Qualifier("jdbc")
@ConditionalOnProperty("hideout.use-mongodb", havingValue = "false", matchIfMissing = true)
class ExposedTimelineRepository(private val idGenerateService: IdGenerateService) : TimelineRepository,
AbstractRepository() {
override val logger: Logger
get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(timeline: Timeline): Timeline = query {
if (Timelines.selectAll().where { Timelines.id eq timeline.id }.forUpdate().singleOrNull() == null) {
Timelines.insert {
it[id] = timeline.id
it[userId] = timeline.userId
it[timelineId] = timeline.timelineId
it[postId] = timeline.postId
it[postActorId] = timeline.postActorId
it[createdAt] = timeline.createdAt
it[replyId] = timeline.replyId
it[repostId] = timeline.repostId
it[visibility] = timeline.visibility.ordinal
it[sensitive] = timeline.sensitive
it[isLocal] = timeline.isLocal
it[isPureRepost] = timeline.isPureRepost
it[mediaIds] = timeline.mediaIds.joinToString(",")
it[emojiIds] = timeline.emojiIds.joinToString(",")
}
} else {
Timelines.update({ Timelines.id eq timeline.id }) {
it[userId] = timeline.userId
it[timelineId] = timeline.timelineId
it[postId] = timeline.postId
it[postActorId] = timeline.postActorId
it[createdAt] = timeline.createdAt
it[replyId] = timeline.replyId
it[repostId] = timeline.repostId
it[visibility] = timeline.visibility.ordinal
it[sensitive] = timeline.sensitive
it[isLocal] = timeline.isLocal
it[isPureRepost] = timeline.isPureRepost
it[mediaIds] = timeline.mediaIds.joinToString(",")
it[emojiIds] = timeline.emojiIds.joinToString(",")
}
}
return@query timeline
}
override suspend fun saveAll(timelines: List<Timeline>): List<Timeline> = query {
Timelines.batchInsert(timelines, true, false) {
this[Timelines.id] = it.id
this[Timelines.userId] = it.userId
this[Timelines.timelineId] = it.timelineId
this[Timelines.postId] = it.postId
this[Timelines.postActorId] = it.postActorId
this[Timelines.createdAt] = it.createdAt
this[Timelines.replyId] = it.replyId
this[Timelines.repostId] = it.repostId
this[Timelines.visibility] = it.visibility.ordinal
this[Timelines.sensitive] = it.sensitive
this[Timelines.isLocal] = it.isLocal
this[Timelines.isPureRepost] = it.isPureRepost
this[Timelines.mediaIds] = it.mediaIds.joinToString(",")
this[Timelines.emojiIds] = it.emojiIds.joinToString(",")
}
return@query timelines
}
override suspend fun findByUserId(id: Long): List<Timeline> = query {
return@query Timelines.selectAll().where { Timelines.userId eq id }.map { it.toTimeline() }
}
override suspend fun findByUserIdAndTimelineId(userId: Long, timelineId: Long): List<Timeline> = query {
return@query Timelines.selectAll().where { Timelines.userId eq userId and (Timelines.timelineId eq timelineId) }
.map { it.toTimeline() }
}
companion object {
private val logger = LoggerFactory.getLogger(ExposedTimelineRepository::class.java)
}
}
fun ResultRow.toTimeline(): Timeline {
return Timeline(
id = this[Timelines.id],
userId = this[Timelines.userId],
timelineId = this[Timelines.timelineId],
postId = this[Timelines.postId],
postActorId = this[Timelines.postActorId],
createdAt = this[Timelines.createdAt],
replyId = this[Timelines.replyId],
repostId = this[Timelines.repostId],
visibility = Visibility.values().first { it.ordinal == this[Timelines.visibility] },
sensitive = this[Timelines.sensitive],
isLocal = this[Timelines.isLocal],
isPureRepost = this[Timelines.isPureRepost],
mediaIds = this[Timelines.mediaIds].split(",").mapNotNull { it.toLongOrNull() },
emojiIds = this[Timelines.emojiIds].split(",").mapNotNull { it.toLongOrNull() }
)
}
object Timelines : Table("timelines") {
val id = long("id")
val userId = long("user_id")
val timelineId = long("timeline_id")
val postId = long("post_id")
val postActorId = long("post_actor_id")
val createdAt = long("created_at")
val replyId = long("reply_id").nullable()
val repostId = long("repost_id").nullable()
val visibility = integer("visibility")
val sensitive = bool("sensitive")
val isLocal = bool("is_local")
val isPureRepost = bool("is_pure_repost")
val mediaIds = varchar("media_ids", 255)
val emojiIds = varchar("emoji_ids", 255)
override val primaryKey: PrimaryKey = PrimaryKey(id)
init {
uniqueIndex(userId, timelineId, postId)
}
}

View File

@ -16,69 +16,67 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.core.domain.model.instance.*
import dev.usbharu.hideout.core.domain.model.instance.InstanceRepository
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.javatime.timestamp import org.jetbrains.exposed.sql.javatime.timestamp
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.net.URI
import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity
@Repository @Repository
class InstanceRepositoryImpl(private val idGenerateService: IdGenerateService) : InstanceRepository, class InstanceRepositoryImpl : InstanceRepository,
AbstractRepository() { AbstractRepository() {
override val logger: Logger override val logger: Logger
get() = Companion.logger get() = Companion.logger
override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(instance: InstanceEntity): InstanceEntity = query { override suspend fun save(instance: InstanceEntity): InstanceEntity = query {
if (Instance.selectAll().where { Instance.id.eq(instance.id) }.forUpdate().empty()) { if (Instance.selectAll().where { Instance.id.eq(instance.id.instanceId) }.forUpdate().empty()) {
Instance.insert { Instance.insert {
it[id] = instance.id it[id] = instance.id.instanceId
it[name] = instance.name it[name] = instance.name.name
it[description] = instance.description it[description] = instance.description.description
it[url] = instance.url it[url] = instance.url.toString()
it[iconUrl] = instance.iconUrl it[iconUrl] = instance.iconUrl.toString()
it[sharedInbox] = instance.sharedInbox it[sharedInbox] = instance.sharedInbox?.toString()
it[software] = instance.software it[software] = instance.software.software
it[version] = instance.version it[version] = instance.version.version
it[isBlocked] = instance.isBlocked it[isBlocked] = instance.isBlocked
it[isMuted] = instance.isMuted it[isMuted] = instance.isMuted
it[moderationNote] = instance.moderationNote it[moderationNote] = instance.moderationNote.note
it[createdAt] = instance.createdAt it[createdAt] = instance.createdAt
} }
} else { } else {
Instance.update({ Instance.id eq instance.id }) { Instance.update({ Instance.id eq instance.id.instanceId }) {
it[name] = instance.name it[name] = instance.name.name
it[description] = instance.description it[description] = instance.description.description
it[url] = instance.url it[url] = instance.url.toString()
it[iconUrl] = instance.iconUrl it[iconUrl] = instance.iconUrl.toString()
it[sharedInbox] = instance.sharedInbox it[sharedInbox] = instance.sharedInbox?.toString()
it[software] = instance.software it[software] = instance.software.software
it[version] = instance.version it[version] = instance.version.version
it[isBlocked] = instance.isBlocked it[isBlocked] = instance.isBlocked
it[isMuted] = instance.isMuted it[isMuted] = instance.isMuted
it[moderationNote] = instance.moderationNote it[moderationNote] = instance.moderationNote.note
it[createdAt] = instance.createdAt it[createdAt] = instance.createdAt
} }
} }
return@query instance return@query instance
} }
override suspend fun findById(id: Long): InstanceEntity? = query { override suspend fun findById(id: InstanceId): InstanceEntity? = query {
return@query Instance.selectAll().where { Instance.id eq id } return@query Instance.selectAll().where { Instance.id eq id.instanceId }
.singleOrNull()?.toInstance() .singleOrNull()?.toInstance()
} }
override suspend fun delete(instance: InstanceEntity): Unit = query { override suspend fun delete(instance: InstanceEntity): Unit = query {
Instance.deleteWhere { id eq instance.id } Instance.deleteWhere { id eq instance.id.instanceId }
} }
override suspend fun findByUrl(url: String): dev.usbharu.hideout.core.domain.model.instance.Instance? = query { override suspend fun findByUrl(url: URI): dev.usbharu.hideout.core.domain.model.instance.Instance? = query {
return@query Instance.selectAll().where { Instance.url eq url }.singleOrNull()?.toInstance() return@query Instance.selectAll().where { Instance.url eq url.toString() }.singleOrNull()?.toInstance()
} }
companion object { companion object {
@ -88,17 +86,17 @@ class InstanceRepositoryImpl(private val idGenerateService: IdGenerateService) :
fun ResultRow.toInstance(): InstanceEntity { fun ResultRow.toInstance(): InstanceEntity {
return InstanceEntity( return InstanceEntity(
id = this[Instance.id], id = InstanceId(this[Instance.id]),
name = this[Instance.name], name = InstanceName(this[Instance.name]),
description = this[Instance.description], description = InstanceDescription(this[Instance.description]),
url = this[Instance.url], url = URI.create(this[Instance.url]),
iconUrl = this[Instance.iconUrl], iconUrl = URI.create(this[Instance.iconUrl]),
sharedInbox = this[Instance.sharedInbox], sharedInbox = this[Instance.sharedInbox]?.let { URI.create(it) },
software = this[Instance.software], software = InstanceSoftware(this[Instance.software]),
version = this[Instance.version], version = InstanceVersion(this[Instance.version]),
isBlocked = this[Instance.isBlocked], isBlocked = this[Instance.isBlocked],
isMuted = this[Instance.isMuted], isMuted = this[Instance.isMuted],
moderationNote = this[Instance.moderationNote], moderationNote = InstanceModerationNote(this[Instance.moderationNote]),
createdAt = this[Instance.createdAt] createdAt = this[Instance.createdAt]
) )
} }

View File

@ -16,69 +16,62 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.application.service.id.IdGenerateService import dev.usbharu.hideout.core.domain.model.media.*
import dev.usbharu.hideout.core.domain.model.media.FileType
import dev.usbharu.hideout.core.domain.model.media.MediaRepository
import dev.usbharu.hideout.core.domain.model.media.MimeType
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.net.URI
import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia
@Repository @Repository
class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : MediaRepository, AbstractRepository() { class MediaRepositoryImpl : MediaRepository, AbstractRepository() {
override val logger: Logger override suspend fun findById(id: MediaId): dev.usbharu.hideout.core.domain.model.media.Media? {
get() = Companion.logger return query {
override suspend fun generateId(): Long = idGenerateService.generateId()
override suspend fun save(media: EntityMedia): EntityMedia = query {
if (Media.selectAll().where { Media.id eq media.id }.forUpdate().singleOrNull() != null
) {
Media.update({ Media.id eq media.id }) {
it[name] = media.name
it[url] = media.url
it[remoteUrl] = media.remoteUrl
it[thumbnailUrl] = media.thumbnailUrl
it[type] = media.type.ordinal
it[blurhash] = media.blurHash
it[mimeType] = media.mimeType.type + "/" + media.mimeType.subtype
it[description] = media.description
}
} else {
Media.insert {
it[id] = media.id
it[name] = media.name
it[url] = media.url
it[remoteUrl] = media.remoteUrl
it[thumbnailUrl] = media.thumbnailUrl
it[type] = media.type.ordinal
it[blurhash] = media.blurHash
it[mimeType] = media.mimeType.type + "/" + media.mimeType.subtype
it[description] = media.description
}
}
return@query media
}
override suspend fun findById(id: Long): EntityMedia? = query {
return@query Media return@query Media
.selectAll().where { Media.id eq id } .selectAll().where { Media.id eq id.id }
.singleOrNull() .singleOrNull()
?.toMedia() ?.toMedia()
} }
}
override suspend fun delete(id: Long): Unit = query { override suspend fun delete(media: dev.usbharu.hideout.core.domain.model.media.Media): Unit = query {
Media.deleteWhere { Media.deleteWhere {
Media.id eq id id eq id
} }
} }
override suspend fun findByRemoteUrl(remoteUrl: String): dev.usbharu.hideout.core.domain.model.media.Media? = override val logger: Logger
query { get() = Companion.logger
return@query Media.selectAll().where { Media.remoteUrl eq remoteUrl }.singleOrNull()?.toMedia()
override suspend fun save(media: EntityMedia): EntityMedia = query {
if (Media.selectAll().where { Media.id eq media.id.id }.forUpdate().singleOrNull() != null
) {
Media.update({ Media.id eq media.id.id }) {
it[name] = media.name.name
it[url] = media.url.toString()
it[remoteUrl] = media.remoteUrl?.toString()
it[thumbnailUrl] = media.thumbnailUrl?.toString()
it[type] = media.type.name
it[blurhash] = media.blurHash?.hash
it[mimeType] = media.mimeType.type + "/" + media.mimeType.subtype
it[description] = media.description?.description
}
} else {
Media.insert {
it[id] = media.id.id
it[name] = media.name.name
it[url] = media.url.toString()
it[remoteUrl] = media.remoteUrl?.toString()
it[thumbnailUrl] = media.thumbnailUrl?.toString()
it[type] = media.type.name
it[blurhash] = media.blurHash?.hash
it[mimeType] = media.mimeType.type + "/" + media.mimeType.subtype
it[description] = media.description?.description
}
}
return@query media
} }
companion object { companion object {
@ -87,34 +80,18 @@ class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : Me
} }
fun ResultRow.toMedia(): EntityMedia { fun ResultRow.toMedia(): EntityMedia {
val fileType = FileType.values().first { it.ordinal == this[Media.type] } val fileType = FileType.valueOf(this[Media.type])
val mimeType = this[Media.mimeType] val mimeType = this[Media.mimeType]
return EntityMedia( return EntityMedia(
id = this[Media.id], id = MediaId(this[Media.id]),
name = this[Media.name], name = MediaName(this[Media.name]),
url = this[Media.url], url = URI.create(this[Media.url]),
remoteUrl = this[Media.remoteUrl], remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) },
thumbnailUrl = this[Media.thumbnailUrl], thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) },
type = fileType, type = fileType,
blurHash = this[Media.blurhash], blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) },
mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType), mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType),
description = this[Media.description] description = this[Media.description]?.let { MediaDescription(it) }
)
}
fun ResultRow.toMediaOrNull(): EntityMedia? {
val fileType = FileType.values().first { it.ordinal == (this.getOrNull(Media.type) ?: return null) }
val mimeType = this.getOrNull(Media.mimeType) ?: return null
return EntityMedia(
id = this.getOrNull(Media.id) ?: return null,
name = this.getOrNull(Media.name) ?: return null,
url = this.getOrNull(Media.url) ?: return null,
remoteUrl = this[Media.remoteUrl],
thumbnailUrl = this[Media.thumbnailUrl],
type = FileType.values().first { it.ordinal == this.getOrNull(Media.type) },
blurHash = this[Media.blurhash],
mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType),
description = this[Media.description]
) )
} }
@ -124,7 +101,7 @@ object Media : Table("media") {
val url = varchar("url", 255).uniqueIndex() val url = varchar("url", 255).uniqueIndex()
val remoteUrl = varchar("remote_url", 255).uniqueIndex().nullable() val remoteUrl = varchar("remote_url", 255).uniqueIndex().nullable()
val thumbnailUrl = varchar("thumbnail_url", 255).uniqueIndex().nullable() val thumbnailUrl = varchar("thumbnail_url", 255).uniqueIndex().nullable()
val type = integer("type") val type = varchar("type", 100)
val blurhash = varchar("blurhash", 255).nullable() val blurhash = varchar("blurhash", 255).nullable()
val mimeType = varchar("mime_type", 255) val mimeType = varchar("mime_type", 255)
val description = varchar("description", 4000).nullable() val description = varchar("description", 4000).nullable()

View File

@ -1,62 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.exposedrepository
import org.jetbrains.exposed.sql.*
import org.springframework.stereotype.Repository
import java.util.*
@Repository
class MetaRepositoryImpl : MetaRepository {
override suspend fun save(meta: dev.usbharu.hideout.core.domain.model.meta.Meta) {
if (Meta.selectAll().where { Meta.id eq 1 }.empty()) {
Meta.insert {
it[id] = 1
it[version] = meta.version
it[kid] = UUID.randomUUID().toString()
it[jwtPrivateKey] = meta.jwt.privateKey
it[jwtPublicKey] = meta.jwt.publicKey
}
} else {
Meta.update({ Meta.id eq 1 }) {
it[version] = meta.version
it[kid] = UUID.randomUUID().toString()
it[jwtPrivateKey] = meta.jwt.privateKey
it[jwtPublicKey] = meta.jwt.publicKey
}
}
}
override suspend fun get(): dev.usbharu.hideout.core.domain.model.meta.Meta? {
return Meta.selectAll().where { Meta.id eq 1 }.singleOrNull()?.let {
dev.usbharu.hideout.core.domain.model.meta.Meta(
it[Meta.version],
Jwt(UUID.fromString(it[Meta.kid]), it[Meta.jwtPrivateKey], it[Meta.jwtPublicKey])
)
}
}
}
object Meta : Table("meta_info") {
val id: Column<Long> = long("id")
val version: Column<String> = varchar("version", 1000)
val kid: Column<String> = varchar("kid", 1000)
val jwtPrivateKey: Column<String> = varchar("jwt_private_key", 100000)
val jwtPublicKey: Column<String> = varchar("jwt_public_key", 100000)
override val primaryKey: PrimaryKey = PrimaryKey(id)
}

View File

@ -16,14 +16,14 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailHashedPassword
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import org.jetbrains.exposed.dao.id.LongIdTable import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.javatime.timestamp
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.update
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
@ -35,24 +35,28 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
override suspend fun save(userDetail: UserDetail): UserDetail = query { override suspend fun save(userDetail: UserDetail): UserDetail = query {
val singleOrNull = val singleOrNull =
UserDetails.selectAll().where { UserDetails.actorId eq userDetail.actorId }.forUpdate().singleOrNull() UserDetails.selectAll().where { UserDetails.id eq userDetail.id.id }.forUpdate().singleOrNull()
if (singleOrNull == null) { if (singleOrNull == null) {
UserDetails.insert { UserDetails.insert {
it[actorId] = userDetail.actorId it[id] = userDetail.id.id
it[password] = userDetail.password it[actorId] = userDetail.actorId.id
it[password] = userDetail.password.password
it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest
it[lastMigration] = userDetail.lastMigration
} }
} else { } else {
UserDetails.update({ UserDetails.actorId eq userDetail.actorId }) { UserDetails.update({ UserDetails.id eq userDetail.id.id }) {
it[password] = userDetail.password it[actorId] = userDetail.actorId.id
it[password] = userDetail.password.password
it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest
it[lastMigration] = userDetail.lastMigration
} }
} }
return@query userDetail return@query userDetail
} }
override suspend fun delete(userDetail: UserDetail): Unit = query { override suspend fun delete(userDetail: UserDetail): Unit = query {
UserDetails.deleteWhere { actorId eq userDetail.actorId } UserDetails.deleteWhere { id eq userDetail.id.id }
} }
override suspend fun findByActorId(actorId: Long): UserDetail? = query { override suspend fun findByActorId(actorId: Long): UserDetail? = query {
@ -60,10 +64,12 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
.selectAll().where { UserDetails.actorId eq actorId } .selectAll().where { UserDetails.actorId eq actorId }
.singleOrNull() .singleOrNull()
?.let { ?.let {
UserDetail( UserDetail.create(
it[UserDetails.actorId], UserDetailId(it[UserDetails.id]),
it[UserDetails.password], ActorId(it[UserDetails.actorId]),
it[UserDetails.autoAcceptFolloweeFollowRequest] UserDetailHashedPassword(it[UserDetails.password]),
it[UserDetails.autoAcceptFolloweeFollowRequest],
it[UserDetails.lastMigration]
) )
} }
} }
@ -73,8 +79,11 @@ class UserDetailRepositoryImpl : UserDetailRepository, AbstractRepository() {
} }
} }
object UserDetails : LongIdTable("user_details") { object UserDetails : Table("user_details") {
val id = long("id")
val actorId = long("actor_id").references(Actors.id) val actorId = long("actor_id").references(Actors.id)
val password = varchar("password", 255) val password = varchar("password", 255)
val autoAcceptFolloweeFollowRequest = bool("auto_accept_followee_follow_request") val autoAcceptFolloweeFollowRequest = bool("auto_accept_followee_follow_request")
val lastMigration = timestamp("last_migration").nullable()
override val primaryKey: PrimaryKey = PrimaryKey(id)
} }

View File

@ -1,41 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.factory
import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.model.actor.ActorDescription
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
import org.springframework.stereotype.Component
@Component
class ActorDescriptionFactoryImpl(
private val applicationConfig: ApplicationConfig,
private val emojiRepository: CustomEmojiRepository,
) : ActorDescription.ActorDescriptionFactory() {
val regex = Regex(":(w+):")
suspend fun create(description: String): ActorDescription {
val findAll = regex.findAll(description)
val emojis =
emojiRepository.findByNamesAndDomain(
findAll.map { it.groupValues[1] }.toList(),
applicationConfig.url.host
)
return create(description, emojis.map { EmojiId(it.id) })
}
}

View File

@ -26,25 +26,23 @@ import java.net.URI
import java.time.Instant import java.time.Instant
@Component @Component
class Actor2FactoryImpl( class ActorFactoryImpl(
private val idGenerateService: IdGenerateService, private val idGenerateService: IdGenerateService,
private val actorScreenNameFactory: ActorScreenNameFactoryImpl,
private val actorDescriptionFactory: ActorDescriptionFactoryImpl,
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
) : Actor2.Actor2Factory() { ) {
suspend fun createLocal( suspend fun createLocal(
name: String, name: String,
keyPair: Pair<ActorPublicKey, ActorPrivateKey>, keyPair: Pair<ActorPublicKey, ActorPrivateKey>,
instanceId: InstanceId, instanceId: InstanceId,
): Actor2 { ): Actor {
val actorName = ActorName(name) val actorName = ActorName(name)
val userUrl = "${applicationConfig.url}/users/${actorName.name}" val userUrl = "${applicationConfig.url}/users/${actorName.name}"
return super.internalCreate( return Actor(
id = ActorId(idGenerateService.generateId()), id = ActorId(idGenerateService.generateId()),
name = actorName, name = actorName,
domain = Domain(applicationConfig.url.host), domain = Domain(applicationConfig.url.host),
screenName = actorScreenNameFactory.create(name), screenName = ActorScreenName(name),
description = actorDescriptionFactory.create(""), description = ActorDescription(""),
inbox = URI.create("$userUrl/inbox"), inbox = URI.create("$userUrl/inbox"),
outbox = URI.create("$userUrl/outbox"), outbox = URI.create("$userUrl/outbox"),
url = applicationConfig.url.toURI(), url = applicationConfig.url.toURI(),
@ -59,8 +57,11 @@ class Actor2FactoryImpl(
followersCount = ActorRelationshipCount(0), followersCount = ActorRelationshipCount(0),
followingCount = ActorRelationshipCount(0), followingCount = ActorRelationshipCount(0),
postsCount = ActorPostsCount(0), postsCount = ActorPostsCount(0),
lastPostDate = null, lastPostAt = null,
suspend = false suspend = false,
emojiIds = emptySet()
) )
} }
} }
// todo なんか色々おかしいので直す

View File

@ -1,45 +0,0 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.infrastructure.factory
import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.model.actor.ActorScreenName
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiRepository
import dev.usbharu.hideout.core.domain.model.emoji.EmojiId
import org.springframework.stereotype.Component
@Component
class ActorScreenNameFactoryImpl(
private val applicationConfig: ApplicationConfig,
private val emojiRepository: CustomEmojiRepository,
) : ActorScreenName.ActorScreenNameFactory() {
val regex = Regex(":(w+):")
suspend fun create(content: String): ActorScreenName {
val findAll = regex.findAll(content)
val emojis =
emojiRepository.findByNamesAndDomain(
findAll.map { it.groupValues[1] }.toList(),
applicationConfig.url.host
)
return create(content, emojis.map { EmojiId(it.id) })
}
}

View File

@ -21,7 +21,7 @@ import dev.usbharu.hideout.application.service.id.IdGenerateService
import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.actor.ActorName import dev.usbharu.hideout.core.domain.model.actor.ActorName
import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.post.Post2 import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.domain.model.post.PostId import dev.usbharu.hideout.core.domain.model.post.PostId
import dev.usbharu.hideout.core.domain.model.post.PostOverview import dev.usbharu.hideout.core.domain.model.post.PostOverview
import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.domain.model.post.Visibility
@ -34,7 +34,7 @@ class PostFactoryImpl(
private val idGenerateService: IdGenerateService, private val idGenerateService: IdGenerateService,
private val postContentFactoryImpl: PostContentFactoryImpl, private val postContentFactoryImpl: PostContentFactoryImpl,
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
) : Post2.PostFactory() { ) : Post.PostFactory() {
suspend fun createLocal( suspend fun createLocal(
actorId: ActorId, actorId: ActorId,
actorName: ActorName, actorName: ActorName,
@ -45,7 +45,7 @@ class PostFactoryImpl(
replyId: PostId?, replyId: PostId?,
sensitive: Boolean, sensitive: Boolean,
mediaIds: List<MediaId>, mediaIds: List<MediaId>,
): Post2 { ): Post {
val id = idGenerateService.generateId() val id = idGenerateService.generateId()
val url = URI.create(applicationConfig.url.toString() + "/users/" + actorName + "/posts/" + id) val url = URI.create(applicationConfig.url.toString() + "/users/" + actorName + "/posts/" + id)
return super.create( return super.create(

View File

@ -16,32 +16,16 @@
package dev.usbharu.hideout.core.interfaces.api.auth package dev.usbharu.hideout.core.interfaces.api.auth
import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.application.config.CaptchaConfig
import org.springframework.stereotype.Controller
import org.springframework.ui.Model import org.springframework.ui.Model
import org.springframework.validation.annotation.Validated import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ModelAttribute import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PostMapping
@Controller interface AuthController {
class AuthController(
private val authApiService: AuthApiService,
private val captchaConfig: CaptchaConfig,
private val applicationConfig: ApplicationConfig
) {
@GetMapping("/auth/sign_up") @GetMapping("/auth/sign_up")
fun signUp(model: Model): String { fun signUp(model: Model): String
model.addAttribute("siteKey", captchaConfig.reCaptchaSiteKey)
model.addAttribute("applicationConfig", applicationConfig)
return "sign_up"
}
@PostMapping("/auth/sign_up") @PostMapping("/auth/sign_up")
suspend fun signUp(@Validated @ModelAttribute signUpForm: SignUpForm): String { suspend fun signUp(@Validated @ModelAttribute signUpForm: SignUpForm): String
return "redirect:" + registerAccount.url
}
} }

View File

@ -16,43 +16,21 @@
package dev.usbharu.hideout.core.interfaces.api.media package dev.usbharu.hideout.core.interfaces.api.media
import dev.usbharu.hideout.application.config.LocalStorageConfig
import dev.usbharu.hideout.core.service.media.FileTypeDeterminationService
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.core.io.ClassPathResource import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.PathResource
import org.springframework.core.io.Resource import org.springframework.core.io.Resource
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PathVariable
import java.nio.file.Path
import kotlin.io.path.name
@Controller @Controller
@ConditionalOnProperty("hideout.storage.type", havingValue = "local", matchIfMissing = true) @ConditionalOnProperty("hideout.storage.type", havingValue = "local", matchIfMissing = true)
class LocalFileController( interface LocalFileController {
localStorageConfig: LocalStorageConfig,
private val fileTypeDeterminationService: FileTypeDeterminationService
) {
private val savePath = Path.of(localStorageConfig.path).toAbsolutePath()
@GetMapping("/files/{id}") @GetMapping("/files/{id}")
fun files(@PathVariable("id") id: String): ResponseEntity<Resource> { fun files(@PathVariable("id") id: String): ResponseEntity<Resource>
val name = Path.of(id).name
val path = savePath.resolve(name)
val mimeType = fileTypeDeterminationService.fileType(path, name)
val pathResource = PathResource(path)
return ResponseEntity
.ok()
.contentType(MediaType(mimeType.type, mimeType.subtype))
.contentLength(pathResource.contentLength())
.body(pathResource)
}
@GetMapping("/users/{user}/icon.jpg", "/users/{user}/header.jpg") @GetMapping("/users/{user}/icon.jpg", "/users/{user}/header.jpg")
fun icons(): ResponseEntity<Resource> { fun icons(): ResponseEntity<Resource> {

View File

@ -44,5 +44,4 @@ object RsaUtil {
} }
fun decodeRsaPrivateKey(encoded: String): RSAPrivateKey = decodeRsaPrivateKey(Base64Util.decode(encoded)) fun decodeRsaPrivateKey(encoded: String): RSAPrivateKey = decodeRsaPrivateKey(Base64Util.decode(encoded))
} }

View File

@ -3,7 +3,7 @@ package dev.usbharu.hideout.core.domain.model.actor
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
class Actors2Test { class ActorsTest {
@Test @Test
fun alsoKnownAsに自分自身が含まれてはいけない() { fun alsoKnownAsに自分自身が含まれてはいけない() {
val actor = TestActor2Factory.create(publicKey = ActorPublicKey("")) val actor = TestActor2Factory.create(publicKey = ActorPublicKey(""))

View File

@ -7,7 +7,7 @@ import kotlinx.coroutines.runBlocking
import java.net.URI import java.net.URI
import java.time.Instant import java.time.Instant
object TestActor2Factory : Actor2.Actor2Factory() { object TestActor2Factory : Actor.Actor2Factory() {
private val idGenerateService = TwitterSnowflakeIdGenerateService private val idGenerateService = TwitterSnowflakeIdGenerateService
fun create( fun create(
@ -31,8 +31,8 @@ object TestActor2Factory : Actor2.Actor2Factory() {
followingCount: Int = 0, followingCount: Int = 0,
postCount: Int = 0, postCount: Int = 0,
lastPostDate: Instant? = null, lastPostDate: Instant? = null,
suspend: Boolean = false suspend: Boolean = false,
): Actor2 { ): Actor {
return runBlocking { return runBlocking {
super.internalCreate( super.internalCreate(
id = ActorId(id), id = ActorId(id),