mirror of https://github.com/usbharu/Hideout.git
Merge pull request #585 from usbharu/ssr-client
noscript用の簡易SSRクライアントを追加
This commit is contained in:
commit
20a762b8f4
|
@ -0,0 +1,38 @@
|
|||
package dev.usbharu.hideout.core.application.actor
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||
import java.net.URI
|
||||
|
||||
data class ActorDetail(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val screenName: String,
|
||||
val host: String,
|
||||
val remoteUrl: String?,
|
||||
val locked: Boolean,
|
||||
val description: String,
|
||||
val postsCount: Int,
|
||||
val iconUrl: URI?,
|
||||
val bannerURL: URI?,
|
||||
val followingCount: Int?,
|
||||
val followersCount: Int?,
|
||||
) {
|
||||
companion object {
|
||||
fun of(actor: Actor, iconUrl: URI?, bannerURL: URI?): ActorDetail {
|
||||
return ActorDetail(
|
||||
id = actor.id.id,
|
||||
name = actor.name.name,
|
||||
screenName = actor.screenName.screenName,
|
||||
host = actor.url.host,
|
||||
remoteUrl = actor.url.toString(),
|
||||
locked = actor.locked,
|
||||
description = actor.description.description,
|
||||
postsCount = actor.postsCount.postsCount,
|
||||
iconUrl = iconUrl,
|
||||
bannerURL = bannerURL,
|
||||
followingCount = actor.followingCount?.relationshipCount,
|
||||
followersCount = actor.followersCount?.relationshipCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package dev.usbharu.hideout.core.application.actor
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.support.acct.Acct
|
||||
|
||||
data class GetActorDetail(
|
||||
val actorName: Acct? = null,
|
||||
val id: Long? = null
|
||||
)
|
|
@ -0,0 +1,49 @@
|
|||
package dev.usbharu.hideout.core.application.actor
|
||||
|
||||
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||
import dev.usbharu.hideout.core.config.ApplicationConfig
|
||||
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.MediaRepository
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class GetActorDetailApplicationService(
|
||||
private val actorRepository: ActorRepository,
|
||||
private val mediaRepository: MediaRepository,
|
||||
private val applicationConfig: ApplicationConfig,
|
||||
transaction: Transaction
|
||||
) :
|
||||
AbstractApplicationService<GetActorDetail, ActorDetail>(
|
||||
transaction,
|
||||
logger
|
||||
) {
|
||||
override suspend fun internalExecute(command: GetActorDetail, principal: Principal): ActorDetail {
|
||||
val actor = if (command.id != null) {
|
||||
actorRepository.findById(ActorId(command.id))
|
||||
?: throw IllegalArgumentException("Actor ${command.id} not found.")
|
||||
} else if (command.actorName != null) {
|
||||
val host = if (command.actorName.host.isEmpty()) {
|
||||
applicationConfig.url.host
|
||||
} else {
|
||||
command.actorName.host
|
||||
}
|
||||
actorRepository.findByNameAndDomain(command.actorName.userpart, host)
|
||||
?: throw IllegalArgumentException("Actor ${command.actorName} not found.")
|
||||
} else {
|
||||
throw IllegalArgumentException("id and actorName are null.")
|
||||
}
|
||||
|
||||
val iconUrl = actor.icon?.let { mediaRepository.findById(it)?.url }
|
||||
val bannerUrl = actor.banner?.let { mediaRepository.findById(it)?.url }
|
||||
|
||||
return ActorDetail.of(actor, iconUrl, bannerUrl)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(GetActorDetailApplicationService::class.java)
|
||||
}
|
||||
}
|
|
@ -19,10 +19,11 @@ class GetLocalInstanceApplicationService(
|
|||
transaction,
|
||||
logger
|
||||
) {
|
||||
var cachedInstance: Instance? = null
|
||||
private var cachedInstance: Instance? = null
|
||||
|
||||
override suspend fun internalExecute(command: Unit, principal: Principal): Instance {
|
||||
if (cachedInstance != null) {
|
||||
logger.trace("Use cache {}", cachedInstance)
|
||||
@Suppress("UnsafeCallOnNullableType")
|
||||
return cachedInstance!!
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package dev.usbharu.hideout.core.application.timeline
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.support.page.Page
|
||||
|
||||
data class GetUserTimeline(val id: Long, val page: Page)
|
|
@ -0,0 +1,53 @@
|
|||
package dev.usbharu.hideout.core.application.timeline
|
||||
|
||||
import dev.usbharu.hideout.core.application.post.PostDetail
|
||||
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||
import dev.usbharu.hideout.core.domain.model.support.page.PaginationList
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import dev.usbharu.hideout.core.query.usertimeline.UserTimelineQueryService
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class GetUserTimelineApplicationService(
|
||||
private val userTimelineQueryService: UserTimelineQueryService,
|
||||
private val postRepository: PostRepository,
|
||||
transaction: Transaction
|
||||
) :
|
||||
AbstractApplicationService<GetUserTimeline, PaginationList<PostDetail, PostId>>(transaction, logger) {
|
||||
override suspend fun internalExecute(
|
||||
command: GetUserTimeline,
|
||||
principal: Principal
|
||||
): PaginationList<PostDetail, PostId> {
|
||||
val postList = postRepository.findByActorIdAndVisibilityInList(
|
||||
ActorId(command.id),
|
||||
listOf(Visibility.PUBLIC, Visibility.UNLISTED, Visibility.FOLLOWERS),
|
||||
command.page
|
||||
)
|
||||
|
||||
val postIdList =
|
||||
postList.mapNotNull { it.repostId } + postList.mapNotNull { it.replyId } + postList.map { it.id }
|
||||
|
||||
val postDetailMap = userTimelineQueryService.findByIdAll(postIdList, principal).associateBy { it.id }
|
||||
|
||||
return PaginationList(
|
||||
postList.mapNotNull {
|
||||
postDetailMap[it.id.id]?.copy(
|
||||
repost = postDetailMap[it.repostId?.id],
|
||||
reply = postDetailMap[it.replyId?.id]
|
||||
)
|
||||
},
|
||||
postList.next,
|
||||
postList.prev
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(GetUserTimelineApplicationService::class.java)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package dev.usbharu.hideout.core.config
|
||||
|
||||
import org.springframework.boot.autoconfigure.context.MessageSourceProperties
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.MessageSource
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource
|
||||
|
||||
@Configuration
|
||||
@Profile("dev")
|
||||
class MessageSourceConfig {
|
||||
@Bean
|
||||
fun messageSource(messageSourceProperties: MessageSourceProperties): MessageSource {
|
||||
val reloadableResourceBundleMessageSource = ReloadableResourceBundleMessageSource()
|
||||
reloadableResourceBundleMessageSource.setBasename("classpath:" + messageSourceProperties.basename)
|
||||
reloadableResourceBundleMessageSource.setCacheSeconds(0)
|
||||
return reloadableResourceBundleMessageSource
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("dev")
|
||||
@ConfigurationProperties(prefix = "spring.messages")
|
||||
fun messageSourceProperties(): MessageSourceProperties = MessageSourceProperties()
|
||||
}
|
|
@ -68,7 +68,7 @@ class SecurityConfig {
|
|||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http)
|
||||
http {
|
||||
exceptionHandling {
|
||||
authenticationEntryPoint = LoginUrlAuthenticationEntryPoint("/login")
|
||||
authenticationEntryPoint = LoginUrlAuthenticationEntryPoint("/auth/sign_in")
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
|
@ -80,20 +80,29 @@ class SecurityConfig {
|
|||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize("/error", permitAll)
|
||||
authorize("/login", permitAll)
|
||||
authorize("/auth/sign_in", permitAll)
|
||||
authorize(GET, "/.well-known/**", permitAll)
|
||||
authorize(GET, "/nodeinfo/2.0", permitAll)
|
||||
|
||||
authorize(GET, "/auth/sign_up", hasRole("ANONYMOUS"))
|
||||
authorize(POST, "/auth/sign_up", permitAll)
|
||||
authorize(GET, "/users/{username}/posts/{postId}", permitAll)
|
||||
authorize(GET, "/users/{userid}", permitAll)
|
||||
authorize(GET, "/files/*", permitAll)
|
||||
authorize(POST, "/publish", authenticated)
|
||||
authorize(GET, "/publish", authenticated)
|
||||
authorize(GET, "/", permitAll)
|
||||
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
formLogin {
|
||||
loginPage = "/auth/sign_in"
|
||||
loginProcessingUrl = "/login"
|
||||
defaultSuccessUrl("/home", false)
|
||||
}
|
||||
logout {
|
||||
logoutUrl = "/auth/sign_out"
|
||||
logoutSuccessUrl = "/auth/sign_in"
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
|
@ -131,6 +140,7 @@ class SecurityConfig {
|
|||
}
|
||||
|
||||
@Bean
|
||||
@Suppress("UnsafeCallOnNullableType")
|
||||
fun loadJwkSource(jwkConfig: JwkConfig, applicationConfig: ApplicationConfig): JWKSource<SecurityContext> {
|
||||
if (jwkConfig.keyId == null) {
|
||||
logger.error("hideout.security.jwt.keyId is null.")
|
||||
|
|
|
@ -16,20 +16,29 @@
|
|||
|
||||
package dev.usbharu.hideout.core.config
|
||||
|
||||
import dev.usbharu.hideout.core.infrastructure.springframework.SPAInterceptor
|
||||
import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.http.converter.HttpMessageConverter
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
|
||||
|
||||
@Configuration
|
||||
class MvcConfigurer(private val jsonOrFormModelMethodProcessor: JsonOrFormModelMethodProcessor) : WebMvcConfigurer {
|
||||
class MvcConfigurer(
|
||||
private val jsonOrFormModelMethodProcessor: JsonOrFormModelMethodProcessor,
|
||||
private val spaInterceptor: SPAInterceptor
|
||||
) : WebMvcConfigurer {
|
||||
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
|
||||
resolvers.add(jsonOrFormModelMethodProcessor)
|
||||
}
|
||||
|
||||
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||
registry.addInterceptor(spaInterceptor)
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
|
|
@ -5,4 +5,14 @@ data class Acct(
|
|||
val host: String
|
||||
) {
|
||||
override fun toString(): String = "acct:$userpart@$host"
|
||||
|
||||
companion object {
|
||||
|
||||
fun of(acct: String): Acct {
|
||||
return Acct(
|
||||
acct.substringBeforeLast('@'),
|
||||
acct.substringAfterLast('@', "")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,5 @@ package dev.usbharu.hideout.core.domain.model.userdetails
|
|||
|
||||
@JvmInline
|
||||
value class UserDetailHashedPassword(val password: String) {
|
||||
override fun toString(): String {
|
||||
return "[MASKED]"
|
||||
}
|
||||
override fun toString(): String = "[MASKED]"
|
||||
}
|
||||
|
|
|
@ -18,7 +18,5 @@ package dev.usbharu.hideout.core.domain.model.userdetails
|
|||
|
||||
@JvmInline
|
||||
value class UserDetailId(val id: Long) {
|
||||
override fun toString(): String {
|
||||
return "UserDetailId(id=$id)"
|
||||
}
|
||||
override fun toString(): String = "UserDetailId(id=$id)"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
package dev.usbharu.hideout.core.infrastructure.exposedquery
|
||||
|
||||
import dev.usbharu.hideout.core.application.post.ActorDetail
|
||||
import dev.usbharu.hideout.core.application.post.MediaDetail
|
||||
import dev.usbharu.hideout.core.application.post.PostDetail
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.*
|
||||
import dev.usbharu.hideout.core.query.usertimeline.UserTimelineQueryService
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.net.URI
|
||||
|
||||
@Repository
|
||||
class ExposedUserTimelineQueryService : UserTimelineQueryService, AbstractRepository() {
|
||||
|
||||
override val logger: Logger
|
||||
get() = Companion.logger
|
||||
|
||||
protected fun authorizedQuery(principal: Principal? = null): QueryAlias {
|
||||
if (principal == null) {
|
||||
return Posts
|
||||
.selectAll()
|
||||
.where {
|
||||
Posts.visibility eq Visibility.PUBLIC.name or (Posts.visibility eq Visibility.UNLISTED.name)
|
||||
}.alias("authorized_table")
|
||||
}
|
||||
|
||||
val relationshipsAlias = Relationships.alias("inverse_relationships")
|
||||
|
||||
return Posts
|
||||
.leftJoin(PostsVisibleActors)
|
||||
.leftJoin(Relationships, onColumn = { Posts.actorId }, otherColumn = { actorId })
|
||||
.leftJoin(
|
||||
relationshipsAlias,
|
||||
onColumn = { Posts.actorId },
|
||||
otherColumn = { relationshipsAlias[Relationships.targetActorId] }
|
||||
)
|
||||
.select(Posts.columns)
|
||||
.where {
|
||||
Posts.visibility eq Visibility.PUBLIC.name or
|
||||
(Posts.visibility eq Visibility.UNLISTED.name) or
|
||||
(Posts.visibility eq Visibility.DIRECT.name and (PostsVisibleActors.actorId eq principal.actorId.id)) or
|
||||
(Posts.visibility eq Visibility.FOLLOWERS.name and (Relationships.blocking eq false and (relationshipsAlias[Relationships.following] eq true))) or
|
||||
(Posts.actorId eq principal.actorId.id)
|
||||
}
|
||||
.alias("authorized_table")
|
||||
}
|
||||
|
||||
override suspend fun findByIdAll(idList: List<PostId>, principal: Principal): List<PostDetail> {
|
||||
val authorizedQuery = authorizedQuery(principal)
|
||||
|
||||
val iconMedia = Media.alias("ICON_MEDIA")
|
||||
|
||||
return authorizedQuery
|
||||
.leftJoin(PostsVisibleActors, { authorizedQuery[Posts.id] }, { PostsVisibleActors.postId })
|
||||
.leftJoin(Actors, { authorizedQuery[Posts.actorId] }, { Actors.id })
|
||||
.leftJoin(iconMedia, { Actors.icon }, { iconMedia[Media.id] })
|
||||
.leftJoin(PostsMedia, { authorizedQuery[Posts.id] }, { PostsMedia.postId })
|
||||
.leftJoin(Media, { PostsMedia.mediaId }, { Media.id })
|
||||
.selectAll()
|
||||
.where { authorizedQuery[Posts.id] inList idList.map { it.id } }
|
||||
.groupBy { it[authorizedQuery[Posts.id]] }
|
||||
.map { it.value }
|
||||
.map {
|
||||
toPostDetail(it.first(), authorizedQuery, iconMedia).copy(
|
||||
mediaDetailList = it.mapNotNull { resultRow ->
|
||||
resultRow.toMediaOrNull()?.let { it1 -> MediaDetail.of(it1) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toPostDetail(it: ResultRow, authorizedQuery: QueryAlias, iconMedia: Alias<Media>): PostDetail {
|
||||
return PostDetail(
|
||||
id = it[authorizedQuery[Posts.id]],
|
||||
actor = ActorDetail(
|
||||
actorId = it[authorizedQuery[Posts.actorId]],
|
||||
instanceId = it[Actors.instance],
|
||||
name = it[Actors.name],
|
||||
domain = it[Actors.domain],
|
||||
screenName = it[Actors.screenName],
|
||||
url = URI.create(it[Actors.url]),
|
||||
locked = it[Actors.locked],
|
||||
icon = it.getOrNull(iconMedia[Media.url])?.let { URI.create(it) }
|
||||
),
|
||||
overview = it[authorizedQuery[Posts.overview]],
|
||||
text = it[authorizedQuery[Posts.text]],
|
||||
content = it[authorizedQuery[Posts.content]],
|
||||
createdAt = it[authorizedQuery[Posts.createdAt]],
|
||||
visibility = Visibility.valueOf(it[authorizedQuery[Posts.visibility]]),
|
||||
pureRepost = false,
|
||||
url = URI.create(it[authorizedQuery[Posts.url]]),
|
||||
apId = URI.create(it[authorizedQuery[Posts.apId]]),
|
||||
repost = null,
|
||||
reply = null,
|
||||
sensitive = it[authorizedQuery[Posts.sensitive]],
|
||||
deleted = it[authorizedQuery[Posts.deleted]],
|
||||
mediaDetailList = emptyList(),
|
||||
moveTo = null
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(ExposedUserTimelineQueryService::class.java)
|
||||
}
|
||||
}
|
|
@ -211,17 +211,38 @@ class ExposedPostRepository(
|
|||
visibilityList: List<Visibility>,
|
||||
of: Page?
|
||||
): PaginationList<Post, PostId> {
|
||||
val postList = query {
|
||||
val query = Posts
|
||||
.selectAll()
|
||||
.where {
|
||||
Posts.actorId eq actorId.id and (visibility inList visibilityList.map { it.name })
|
||||
}
|
||||
|
||||
if (of?.minId != null) {
|
||||
query.orderBy(Posts.createdAt, SortOrder.ASC)
|
||||
of.minId?.let { query.andWhere { Posts.id greater it } }
|
||||
of.maxId?.let { query.andWhere { Posts.id less it } }
|
||||
} else {
|
||||
query.orderBy(Posts.createdAt, SortOrder.DESC)
|
||||
of?.sinceId?.let { query.andWhere { Posts.id greater it } }
|
||||
of?.maxId?.let { query.andWhere { Posts.id less it } }
|
||||
}
|
||||
|
||||
of?.limit?.let { query.limit(it) }
|
||||
|
||||
query.let(postQueryMapper::map)
|
||||
}
|
||||
|
||||
val posts = if (of?.minId != null) {
|
||||
postList.reversed()
|
||||
} else {
|
||||
postList
|
||||
}
|
||||
|
||||
return PaginationList(
|
||||
query {
|
||||
Posts
|
||||
.selectAll()
|
||||
.where {
|
||||
Posts.actorId eq actorId.id and (visibility inList visibilityList.map { it.name })
|
||||
}
|
||||
.let(postQueryMapper::map)
|
||||
},
|
||||
null,
|
||||
null
|
||||
posts,
|
||||
posts.lastOrNull()?.id,
|
||||
posts.firstOrNull()?.id
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,23 @@ fun ResultRow.toMedia(): EntityMedia {
|
|||
)
|
||||
}
|
||||
|
||||
fun ResultRow.toMediaOrNull(): EntityMedia? {
|
||||
val fileType = FileType.valueOf(this.getOrNull(Media.type) ?: return null)
|
||||
val mimeType = this.getOrNull(Media.mimeType) ?: return null
|
||||
return EntityMedia(
|
||||
id = MediaId(this.getOrNull(Media.id) ?: return null),
|
||||
name = MediaName(this.getOrNull(Media.name) ?: return null),
|
||||
url = URI.create(this.getOrNull(Media.url) ?: return null),
|
||||
remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) },
|
||||
thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) },
|
||||
type = FileType.valueOf(this[Media.type]),
|
||||
blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) },
|
||||
mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType),
|
||||
description = this[Media.description]?.let { MediaDescription(it) },
|
||||
actorId = ActorId(this[Media.actorId])
|
||||
)
|
||||
}
|
||||
|
||||
object Media : Table("media") {
|
||||
val id = long("id")
|
||||
val name = varchar("name", 255)
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package dev.usbharu.hideout.core.infrastructure.springframework
|
||||
|
||||
import dev.usbharu.hideout.core.interfaces.web.common.OGP
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.servlet.HandlerInterceptor
|
||||
import org.springframework.web.servlet.ModelAndView
|
||||
|
||||
@Component
|
||||
class SPAInterceptor : HandlerInterceptor {
|
||||
|
||||
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
|
||||
if (request.getParameter("s") == "f") {
|
||||
request.session.setAttribute("s", "f")
|
||||
} else if (request.getParameter("s") == "t") {
|
||||
request.session.setAttribute("s", "t")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun postHandle(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
handler: Any,
|
||||
modelAndView: ModelAndView?
|
||||
) {
|
||||
if (modelAndView?.viewName == "error") {
|
||||
return
|
||||
}
|
||||
|
||||
if (request.session.getAttribute("s") == "f") {
|
||||
return
|
||||
}
|
||||
|
||||
val ogp = modelAndView?.modelMap?.get("ogp") as? OGP
|
||||
|
||||
modelAndView?.clear()
|
||||
modelAndView?.addObject("nsUrl", request.requestURI + "?s=f" + request.queryString?.let { "&$it" }.orEmpty())
|
||||
modelAndView?.addObject("ogp", ogp)
|
||||
modelAndView?.viewName = "index"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package dev.usbharu.hideout.core.interfaces.web
|
||||
|
||||
import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService
|
||||
import dev.usbharu.hideout.core.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.ui.Model
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
||||
@Controller
|
||||
class IndexController(
|
||||
private val applicationConfig: ApplicationConfig,
|
||||
private val springSecurityFormLoginPrincipalContextHolder: SpringSecurityFormLoginPrincipalContextHolder,
|
||||
private val getLocalInstanceApplicationService: GetLocalInstanceApplicationService
|
||||
) {
|
||||
@GetMapping("/")
|
||||
suspend fun index(model: Model): String {
|
||||
if (springSecurityFormLoginPrincipalContextHolder.getPrincipal().userDetailId != null) {
|
||||
return "redirect:/home"
|
||||
}
|
||||
|
||||
val instance = getLocalInstanceApplicationService.execute(Unit, Anonymous)
|
||||
model.addAttribute("instance", instance)
|
||||
model.addAttribute("applicationConfig", applicationConfig)
|
||||
return "top"
|
||||
}
|
||||
}
|
|
@ -14,13 +14,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.usbharu.hideout.core.interfaces.api.auth
|
||||
package dev.usbharu.hideout.core.interfaces.web.auth
|
||||
|
||||
import dev.usbharu.hideout.core.application.actor.RegisterLocalActor
|
||||
import dev.usbharu.hideout.core.application.actor.RegisterLocalActorApplicationService
|
||||
import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService
|
||||
import dev.usbharu.hideout.core.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.ui.Model
|
||||
import org.springframework.validation.annotation.Validated
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.ModelAttribute
|
||||
|
@ -28,11 +31,16 @@ import org.springframework.web.bind.annotation.PostMapping
|
|||
|
||||
@Controller
|
||||
class AuthController(
|
||||
private val applicationConfig: ApplicationConfig,
|
||||
private val registerLocalActorApplicationService: RegisterLocalActorApplicationService,
|
||||
private val getLocalInstanceApplicationService: GetLocalInstanceApplicationService,
|
||||
) {
|
||||
@GetMapping("/auth/sign_up")
|
||||
@Suppress("FunctionOnlyReturningConstant")
|
||||
fun signUp(): String = "sign_up"
|
||||
suspend fun signUp(model: Model): String {
|
||||
model.addAttribute("instance", getLocalInstanceApplicationService.execute(Unit, Anonymous))
|
||||
return "sign_up"
|
||||
}
|
||||
|
||||
@PostMapping("/auth/sign_up")
|
||||
suspend fun signUp(@Validated @ModelAttribute signUpForm: SignUpForm, request: HttpServletRequest): String {
|
||||
|
@ -44,4 +52,14 @@ class AuthController(
|
|||
request.login(signUpForm.username, signUpForm.password)
|
||||
return "redirect:$uri"
|
||||
}
|
||||
|
||||
@GetMapping("/auth/sign_in")
|
||||
suspend fun signIn(model: Model): String {
|
||||
model.addAttribute("applicationConfig", applicationConfig)
|
||||
return "sign_in"
|
||||
}
|
||||
|
||||
@GetMapping("/auth/sign_out")
|
||||
@Suppress("FunctionOnlyReturningConstant")
|
||||
fun signOut(): String = "sign_out"
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package dev.usbharu.hideout.core.interfaces.api.auth
|
||||
package dev.usbharu.hideout.core.interfaces.web.auth
|
||||
|
||||
data class SignUpForm(
|
||||
val username: String,
|
|
@ -0,0 +1,8 @@
|
|||
package dev.usbharu.hideout.core.interfaces.web.common
|
||||
|
||||
data class OGP(
|
||||
val title: String,
|
||||
val url: String,
|
||||
val description: String,
|
||||
val image: String?
|
||||
)
|
|
@ -13,6 +13,7 @@ import org.springframework.ui.Model
|
|||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.ModelAttribute
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
|
||||
@Controller
|
||||
class PublishController(
|
||||
|
@ -22,7 +23,7 @@ class PublishController(
|
|||
private val userRegisterLocalPostApplicationService: RegisterLocalPostApplicationService
|
||||
) {
|
||||
@GetMapping("/publish")
|
||||
suspend fun publish(model: Model): String {
|
||||
suspend fun publish(model: Model, @RequestParam("reply_to") replyTo: Long?, @RequestParam repost: Long?): String {
|
||||
val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
||||
|
||||
if (principal.userDetailId == null) {
|
||||
|
@ -35,7 +36,7 @@ class PublishController(
|
|||
val userDetail = getUserDetailApplicationService.execute(GetUserDetail(principal.userDetailId!!.id), principal)
|
||||
model.addAttribute("instance", instance)
|
||||
model.addAttribute("user", userDetail)
|
||||
model.addAttribute("form", PublishPost())
|
||||
model.addAttribute("form", PublishPost(reply_to = replyTo, repost = repost))
|
||||
return "post-postForm"
|
||||
}
|
||||
|
||||
|
@ -50,8 +51,8 @@ class PublishController(
|
|||
content = publishPost.status.orEmpty(),
|
||||
overview = publishPost.overview,
|
||||
visibility = Visibility.valueOf(publishPost.visibility.uppercase()),
|
||||
repostId = null,
|
||||
replyId = null,
|
||||
repostId = publishPost.repost,
|
||||
replyId = publishPost.reply_to,
|
||||
sensitive = false,
|
||||
mediaIds = emptyList()
|
||||
)
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
package dev.usbharu.hideout.core.interfaces.web.posts
|
||||
|
||||
data class PublishPost(var status: String? = null, var overview: String? = null, var visibility: String = "PUBLIC")
|
||||
@Suppress("ConstructorParameterNaming")
|
||||
data class PublishPost(
|
||||
var status: String? = null,
|
||||
var overview: String? = null,
|
||||
var visibility: String = "PUBLIC",
|
||||
var reply_to: Long? = null,
|
||||
var repost: Long? = null
|
||||
)
|
||||
|
|
|
@ -22,9 +22,9 @@ class TimelineController(
|
|||
@GetMapping("/home")
|
||||
suspend fun homeTimeline(
|
||||
model: Model,
|
||||
@RequestParam sinceId: String?,
|
||||
@RequestParam maxId: String?,
|
||||
@RequestParam minId: String?
|
||||
@RequestParam("since_id") sinceId: String?,
|
||||
@RequestParam("max_id") maxId: String?,
|
||||
@RequestParam("min_id") minId: String?
|
||||
): String {
|
||||
val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
||||
val userDetail = transaction.transaction {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package dev.usbharu.hideout.core.interfaces.web.user
|
||||
|
||||
import dev.usbharu.hideout.core.application.actor.GetActorDetail
|
||||
import dev.usbharu.hideout.core.application.actor.GetActorDetailApplicationService
|
||||
import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService
|
||||
import dev.usbharu.hideout.core.application.timeline.GetUserTimeline
|
||||
import dev.usbharu.hideout.core.application.timeline.GetUserTimelineApplicationService
|
||||
import dev.usbharu.hideout.core.domain.model.support.acct.Acct
|
||||
import dev.usbharu.hideout.core.domain.model.support.page.Page
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.ui.Model
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
|
||||
@Controller
|
||||
class UserController(
|
||||
private val getLocalInstanceApplicationService: GetLocalInstanceApplicationService,
|
||||
private val getUserDetailApplicationService: GetActorDetailApplicationService,
|
||||
private val springSecurityFormLoginPrincipalContextHolder: SpringSecurityFormLoginPrincipalContextHolder,
|
||||
private val getUserTimelineApplicationService: GetUserTimelineApplicationService
|
||||
) {
|
||||
@GetMapping("/users/{name}")
|
||||
suspend fun userById(
|
||||
@PathVariable name: String,
|
||||
@RequestParam("min_id") minId: Long?,
|
||||
@RequestParam("max_id") maxId: Long?,
|
||||
@RequestParam("since_id") sinceId: Long?,
|
||||
model: Model
|
||||
): String {
|
||||
val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
||||
|
||||
model.addAttribute("instance", getLocalInstanceApplicationService.execute(Unit, Anonymous))
|
||||
val actorDetail = getUserDetailApplicationService.execute(GetActorDetail(Acct.of(name)), principal)
|
||||
model.addAttribute(
|
||||
"user",
|
||||
actorDetail
|
||||
)
|
||||
model.addAttribute(
|
||||
"userTimeline",
|
||||
getUserTimelineApplicationService.execute(
|
||||
GetUserTimeline(
|
||||
actorDetail.id,
|
||||
Page.of(maxId, sinceId, minId, 20)
|
||||
),
|
||||
principal
|
||||
)
|
||||
)
|
||||
return "userById"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package dev.usbharu.hideout.core.query.usertimeline
|
||||
|
||||
import dev.usbharu.hideout.core.application.post.PostDetail
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
|
||||
interface UserTimelineQueryService {
|
||||
/**
|
||||
* replyやrepost等はnullになります
|
||||
*/
|
||||
suspend fun findByIdAll(idList: List<PostId>, principal: Principal): List<PostDetail>
|
||||
}
|
|
@ -45,11 +45,7 @@ object RsaUtil {
|
|||
|
||||
fun decodeRsaPrivateKey(encoded: String): RSAPrivateKey = decodeRsaPrivateKey(Base64Util.decode(encoded))
|
||||
|
||||
fun encodeRsaPublicKey(publicKey: RSAPublicKey): String {
|
||||
return Base64Util.encode(publicKey.encoded)
|
||||
}
|
||||
fun encodeRsaPublicKey(publicKey: RSAPublicKey): String = Base64Util.encode(publicKey.encoded)
|
||||
|
||||
fun encodeRsaPrivateKey(privateKey: RSAPrivateKey): String {
|
||||
return Base64Util.encode(privateKey.encoded)
|
||||
}
|
||||
fun encodeRsaPrivateKey(privateKey: RSAPrivateKey): String = Base64Util.encode(privateKey.encoded)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ spring:
|
|||
virtual:
|
||||
enabled: true
|
||||
messages:
|
||||
basename: messages.hideout-web-messages
|
||||
basename: messages/hideout-web-messages
|
||||
cache-duration: -1
|
||||
thymeleaf:
|
||||
cache: false
|
||||
server:
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
auth-signIn.title=\u30ED\u30B0\u30A4\u30F3 - {0}
|
||||
auth-signUp.password=\u30D1\u30B9\u30EF\u30FC\u30C9
|
||||
auth-signUp.register=\u767B\u9332\u3059\u308B
|
||||
auth-signUp.username=\u30E6\u30FC\u30B6\u30FC\u540D
|
||||
common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA
|
||||
common.audio-download-link=\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
||||
common.empty=\u8868\u793A\u3059\u308B\u3082\u306E\u304C\u3042\u308A\u307E\u305B\u3093
|
||||
|
@ -16,6 +20,12 @@ post-form.new-posts=\u65B0\u3057\u3044\u6295\u7A3F!
|
|||
post-form.new-posts-cw=CW
|
||||
post-form.new-posts-cw-title=\u30B3\u30F3\u30C6\u30F3\u30C4\u306B\u95B2\u89A7\u6CE8\u610F\u3092\u3064\u3051\u308B
|
||||
post-form.new-posts-form-label=\u4ECA\u306A\u306B\u3057\u3066\u308B?
|
||||
post-form.new-posts-replyTo=\u8FD4\u4FE1\u5148
|
||||
post-form.new-posts-submit=\u6295\u7A3F\u3059\u308B
|
||||
post-form.new-posts.repost=\u30EA\u30DD\u30B9\u30C8
|
||||
post.repost=\u30EA\u30DD\u30B9\u30C8
|
||||
post.repost-by={0}\u304C\u30EA\u30DD\u30B9\u30C8
|
||||
post.repost-by={0}\u304C\u30EA\u30DD\u30B9\u30C8
|
||||
user-by-id.followersCount={0} \u30D5\u30A9\u30ED\u30EF\u30FC
|
||||
user-by-id.followingCount={0} \u30D5\u30A9\u30ED\u30FC\u4E2D
|
||||
user-by-id.postsCount={0} \u6295\u7A3F
|
||||
user-by-id.title={0} \u3055\u3093 - {1}
|
|
@ -1,3 +1,7 @@
|
|||
auth-signIn.title=Sign in - {0}
|
||||
auth-signUp.password=Password
|
||||
auth-signUp.register=Register Account
|
||||
auth-signUp.username=Username
|
||||
common.audio=Audio
|
||||
common.audio-download-link=Download the audio.
|
||||
common.empty=Empty
|
||||
|
@ -15,6 +19,11 @@ post-form.new-posts=New Posts!
|
|||
post-form.new-posts-cw=CW
|
||||
post-form.new-posts-cw-title=Add content warning
|
||||
post-form.new-posts-form-label=What's on your mind?
|
||||
post-form.new-posts-replyTo=Reply to
|
||||
post-form.new-posts-submit=Submit!
|
||||
post-form.new-posts.repost=Repost
|
||||
post.repost=Repost
|
||||
post.repost-by=Repost by {0}
|
||||
post.repost-by=Repost by {0}
|
||||
user-by-id.followersCount={0} Followers
|
||||
user-by-id.followingCount={0} Following
|
||||
user-by-id.postsCount={0} Posts
|
|
@ -1,3 +1,7 @@
|
|||
auth-signIn.title=\u30ED\u30B0\u30A4\u30F3 - {0}
|
||||
auth-signUp.password=\u30D1\u30B9\u30EF\u30FC\u30C9
|
||||
auth-signUp.register=\u767B\u9332\u3059\u308B
|
||||
auth-signUp.username=\u30E6\u30FC\u30B6\u30FC\u540D
|
||||
common.audio=\u30AA\u30FC\u30C7\u30A3\u30AA
|
||||
common.audio-download-link=\u97F3\u58F0\u30D5\u30A1\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
||||
common.empty=\u8868\u793A\u3059\u308B\u3082\u306E\u304C\u3042\u308A\u307E\u305B\u3093
|
||||
|
@ -16,6 +20,12 @@ post-form.new-posts=\u65B0\u3057\u3044\u6295\u7A3F!
|
|||
post-form.new-posts-cw=CW
|
||||
post-form.new-posts-cw-title=\u30B3\u30F3\u30C6\u30F3\u30C4\u306B\u95B2\u89A7\u6CE8\u610F\u3092\u3064\u3051\u308B
|
||||
post-form.new-posts-form-label=\u4ECA\u306A\u306B\u3057\u3066\u308B?
|
||||
post-form.new-posts-replyTo=\u8FD4\u4FE1\u5148
|
||||
post-form.new-posts-submit=\u6295\u7A3F\u3059\u308B
|
||||
post-form.new-posts.repost=\u30EA\u30DD\u30B9\u30C8
|
||||
post.repost=\u30EA\u30DD\u30B9\u30C8
|
||||
post.repost-by={0}\u304C\u30EA\u30DD\u30B9\u30C8
|
||||
post.repost-by={0}\u304C\u30EA\u30DD\u30B9\u30C8
|
||||
user-by-id.followersCount={0} \u30D5\u30A9\u30ED\u30EF\u30FC
|
||||
user-by-id.followingCount={0} \u30D5\u30A9\u30ED\u30FC\u4E2D
|
||||
user-by-id.postsCount={0} \u6295\u7A3F
|
||||
user-by-id.title={0} \u3055\u3093 - {1}
|
|
@ -11,7 +11,8 @@
|
|||
<!--/*@thymesVar id="post" type="dev.usbharu.hideout.core.application.post.PostDetail"*/-->
|
||||
<img alt="" height="80px" src="" th:src="${post.actor.icon}" width="80px">
|
||||
<div style="display: inline-block">
|
||||
<p th:text="${post.actor.screenName}+'('+${post.actor.name}+'@'+${post.actor.domain}+')'"></p>
|
||||
<a th:href="${post.actor.url}"
|
||||
th:text="${post.actor.screenName}+'(@'+${post.actor.name}+'@'+${post.actor.domain}+')'"></a>
|
||||
</div>
|
||||
|
||||
<div th:utext="${post.content}">
|
||||
|
@ -43,6 +44,7 @@
|
|||
|
||||
<div class="post-controller" th:fragment="single-post-controller(post)">
|
||||
<!--/*@thymesVar id="post" type="dev.usbharu.hideout.core.application.post.PostDetail"*/-->
|
||||
<a th:href="${'/publish?reply_to=' + post.id}">Reply</a>
|
||||
<a th:href="${post.apId}">
|
||||
<time th:datetime="${post.createdAt}" th:text="${#temporals.format(post.createdAt, 'yyyy-MM-dd HH:mm')}"></time>
|
||||
</a>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<th:block th:fragment="simple-timline(timelineObject,href)">
|
||||
<!--/*@thymesVar id="timelineObject" type="dev.usbharu.hideout.core.domain.model.support.page.PaginationList<dev.usbharu.hideout.core.application.post.PostDetail,dev.usbharu.hideout.core.domain.model.post.PostId>"*/-->
|
||||
<div th:if="${timelineObject.prev != null}">
|
||||
<a th:href="${href + '?minId=' + timelineObject.prev.id}" th:text="#{common.paging-load}">Show more</a>
|
||||
<a th:href="${href + '?min_id=' + timelineObject.prev.id}" th:text="#{common.paging-load}">Show more</a>
|
||||
</div>
|
||||
<div th:if="${timelineObject.isEmpty()}" th:text="#{common.empty}"></div>
|
||||
<div th:each="postDetail : ${timelineObject}">
|
||||
|
@ -16,7 +16,7 @@
|
|||
<th:block th:replace="~{fragments-post :: single-post-controller(${postDetail})}"></th:block>
|
||||
</div>
|
||||
<div th:if="${timelineObject.next != null}">
|
||||
<a th:href="${href + '?maxId=' + timelineObject.next.id}" th:text="#{common.paging-load}">Show more</a>
|
||||
<a th:href="${href + '?max_id=' + timelineObject.next.id}" th:text="#{common.paging-load}">Show more</a>
|
||||
</div>
|
||||
</th:block>
|
||||
</body>
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<html lang="en" th:replace="~{layout::layout(${ogp}, ~{::#content})}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<div th:replace="~{fragments-timeline :: simple-timline(${timeline},'/home')}"></div>
|
||||
</noscript>
|
||||
<div id="content" th:replace="~{fragments-timeline :: simple-timline(${timeline},'/home')}"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head prefix="og: http://ogp.me/ns#">
|
||||
<meta charset="UTF-8">
|
||||
<title th:title="${ogp?.title}">Index</title>
|
||||
<meta property="og:url" th:content="${ogp?.url}">
|
||||
<meta property="og:title" th:content="${ogp?.title}">
|
||||
<meta property="og:description" th:content="${ogp?.description}">
|
||||
<th:block th:if="${ogp?.image != null}">
|
||||
<meta property="og:image" th:content="${ogp.image}">
|
||||
</th:block>
|
||||
<noscript>
|
||||
<meta http-equiv="refresh" th:content="${'1; url='+nsUrl}">
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<a th:href="${nsUrl}">No Script</a>
|
||||
</noscript>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" th:fragment="layout(ogp, content)" xmlns:th="http://www.thymeleaf.org">
|
||||
<head prefix="og: http://ogp.me/ns#">
|
||||
<meta charset="UTF-8">
|
||||
<!--/*@thymesVar id="ogp" type="dev.usbharu.hideout.core.interfaces.web.common.OGP"*/-->
|
||||
<title th:title="${ogp?.title}">Index</title>
|
||||
<meta property="og:url" th:content="${ogp?.url}">
|
||||
<meta property="og:title" th:content="${ogp?.title}">
|
||||
<meta property="og:description" th:content="${ogp?.description}">
|
||||
<th:block th:if="${ogp?.image != null}">
|
||||
<meta property="og:image" th:content="${ogp.image}">
|
||||
</th:block>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="/home">Hideout</a><a href="/publish">New Post</a><a href="/users/1"><img height="80px"
|
||||
src="/users/i/icon.jpg" width="80px"></a>
|
||||
</header>
|
||||
<hr>
|
||||
<main>
|
||||
<div th:replace="${content}"></div>
|
||||
</main>
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
|
||||
<html lang="ja" th:replace="~{layout::layout(${ogp}, ~{::#content})}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head prefix="og: http://ogp.me/ns#">
|
||||
<meta charset="UTF-8">
|
||||
<title th:text="#{post-form.new-posts}">New Posts!</title>
|
||||
|
@ -7,8 +7,8 @@
|
|||
<meta property="og:title" th:content="#{post-form.new-posts}">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
|
||||
<noscript>
|
||||
<div>
|
||||
<img alt="" height="80px" src="" th:src="${user.iconUrl}" width="80px">
|
||||
<div style="display: inline-block">
|
||||
|
@ -16,6 +16,14 @@
|
|||
</div>
|
||||
</div>
|
||||
<form action="/publish" method="post" th:action="@{/publish}" th:object="${form}">
|
||||
<div>
|
||||
<label for="status-form-replyto" th:text="#{post-form.new-posts-replyTo}">Reply to</label>
|
||||
<input id="status-form-replyto" name="reply_to" th:value="${form.reply_to}" type="number">
|
||||
</div>
|
||||
<div>
|
||||
<label for="status-form-repost" th:text="#{post-form.new-posts.repost}">Repost</label>
|
||||
<input id="status-form-repost" name="repost" type="number" th:value="${form.repost}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="stats-form-overview" th:text="#{post-form.new-posts-cw}"
|
||||
th:title="#{post-form.new-posts-cw-title}"
|
||||
|
@ -31,13 +39,16 @@
|
|||
<fieldset>
|
||||
<legend th:text="#{common.visibility}">Visibility</legend>
|
||||
<label for="status-form-visibility-public" th:text="#{common.visibility-public}">Public</label>
|
||||
<input id="status-form-visibility-public" name="visibility" th:checked="${form.visibility == 'PUBLIC'}" type="radio"
|
||||
<input id="status-form-visibility-public" name="visibility" th:checked="${form.visibility == 'PUBLIC'}"
|
||||
type="radio"
|
||||
value="PUBLIC">
|
||||
<label for="status-form-visibility-unlisted" th:text="#{common.visibility-unlisted}">Unlisted</label>
|
||||
<input id="status-form-visibility-unlisted" name="visibility" th:checked="${form.visibility == 'UNLISTED'}" type="radio"
|
||||
<input id="status-form-visibility-unlisted" name="visibility"
|
||||
th:checked="${form.visibility == 'UNLISTED'}" type="radio"
|
||||
value="UNLISTED">
|
||||
<label for="status-form-visibility-followers" th:text="#{common.visibility-followers}">Followers</label>
|
||||
<input id="status-form-visibility-followers" name="visibility" th:checked="${form.visibility == 'FOLLOWERS'}" type="radio"
|
||||
<input id="status-form-visibility-followers" name="visibility"
|
||||
th:checked="${form.visibility == 'FOLLOWERS'}" type="radio"
|
||||
value="FOLLOWERS">
|
||||
</fieldset>
|
||||
</div>
|
||||
|
@ -45,7 +56,7 @@
|
|||
<input th:value="#{post-form.new-posts-submit}" type="submit" value="Publish!">
|
||||
</div>
|
||||
</form>
|
||||
</noscript>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,24 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
|
||||
<html lang="ja" th:replace="~{layout::layout(${ogp},~{::#content})}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head prefix="og: http://ogp.me/ns#">
|
||||
<meta charset="UTF-8">
|
||||
<title th:text="#{post-by-id.title(${post.actor.screenName},${instance.name})}">Posts - hideout</title>
|
||||
<meta property="og:url" th:content="${post.url}">
|
||||
<meta property="og:title" th:content="#{post-by-id.title(${post.actor.screenName},${instance.name})}">
|
||||
<meta property="og:description" th:content="${post.text}">
|
||||
<th:block
|
||||
th:if="${post.mediaDetailList.isEmpty() || (post.mediaDetailList.get(0).thumbnailUrl == null && (post.mediaDetailList.get(0).type != 'Image' || post.mediaDetailList.get(0).type != 'Video'))}">
|
||||
<meta property="og:image" th:content="${post.actor.icon}">
|
||||
</th:block>
|
||||
<th:block
|
||||
th:unless="${post.mediaDetailList.isEmpty() || (post.mediaDetailList.get(0).thumbnailUrl == null && (post.mediaDetailList.get(0).type != 'Image' || post.mediaDetailList.get(0).type != 'Video'))}">
|
||||
<meta property="og:image" th:content="${post.mediaDetailList.get(0).thumbnailUrl}">
|
||||
</th:block>
|
||||
|
||||
<title>Posts - hideout</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
|
||||
<div id="content">
|
||||
<th:block th:if=" ${post.reply != null}">
|
||||
<th:block th:replace="~{fragments-post :: single-simple-post(${post.reply})}"></th:block>
|
||||
<hr>
|
||||
|
@ -40,6 +29,7 @@
|
|||
<th:block th:replace="={fragments-post :: single-simple-post(${post.repost})}"></th:block>
|
||||
<cite th:text="${post.repost.apId}"></cite>
|
||||
</th:block>
|
||||
</noscript>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title th:text="#{auth-signIn.title(${applicationConfig.url})}">Sign In - Hideout</title>
|
||||
</head>
|
||||
<body>
|
||||
<div th:if="${param.error}">
|
||||
Invalid username and password.
|
||||
</div>
|
||||
<div th:if="${param.logout}">
|
||||
You have been logged out.
|
||||
</div>
|
||||
<form method="post" th:action="@{/login}">
|
||||
<div>
|
||||
<input name="username" placeholder="Username" type="text"/>
|
||||
</div>
|
||||
<div>
|
||||
<input name="password" placeholder="Password" type="password"/>
|
||||
</div>
|
||||
<input type="submit" value="Log in"/>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form method="post" th:action="@{/auth/sign_out}">
|
||||
<input name="logout" type="submit" value="Sign out">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -2,13 +2,21 @@
|
|||
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>SignUp</title>
|
||||
<title th:text="#{auth-signUp.register}">Register Account</title>
|
||||
<meta property="og:url" th:content="${instance.url + '/auth/sign_up'}">
|
||||
<meta property="og:title" th:content="#{auth-signUp.register}">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<form method='post' th:action="@{/auth/sign_up}">
|
||||
<input name='username' type='text' value=''>
|
||||
<input name='password' type='password'>
|
||||
<div>
|
||||
<label for="signUp-form-username" th:text="#{auth-signUp.username}">Username</label>
|
||||
<input id="signUp-form-username" name='username' type='text' value=''>
|
||||
</div>
|
||||
<div>
|
||||
<label for="signUp-form-password" th:text="#{auth-signUp.password}">Password</label>
|
||||
<input id="signUp-form-password" name='password' type='password'>
|
||||
</div>
|
||||
<input type="submit">
|
||||
</form>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title th:title="${title}">Index</title>
|
||||
<meta property="og:url" th:content="${url}">
|
||||
<meta property="og:title" th:content="${title}">
|
||||
<meta property="og:description" th:content="${description}">
|
||||
<meta property="og:image" th:content="${image}">
|
||||
</head>
|
||||
<body>
|
||||
<h1 th:text="${instance.name + ' - Hideout'}">Hideout</h1>
|
||||
<div><a href="/auth/sign_up" th:unless="${applicationConfig.private}">Sign up</a> <a href="/auth/sign_in">Sign in</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" th:replace="~{layout::layout(${ogp}, ~{::#content})}" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>User - hideout</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<div>
|
||||
<img alt="" height="150px" th:src="${user.iconUrl}" width="150px">
|
||||
<img alt="" height="150px" th:src="${user.bannerURL}" width="600px">
|
||||
</div>
|
||||
<div>
|
||||
<th:block th:if="${user.locked}">
|
||||
<h2 th:text="${user.screenName} + '(private)'"></h2>
|
||||
</th:block>
|
||||
|
||||
<th:block th:if="!${user.locked}">
|
||||
<h2 th:text="${user.screenName}"></h2>
|
||||
</th:block>
|
||||
<p th:text="'@'+${user.name} + '@' + ${user.host}"></p>
|
||||
</div>
|
||||
<div>
|
||||
<p th:text="${user.description}"></p>
|
||||
</div>
|
||||
<div>
|
||||
<p th:if="user.postsCount != null" th:text="#{user-by-id.postsCount(${user.postsCount})}">0 Posts</p>
|
||||
<p th:if="user.followingCount != null" th:text="#{user-by-id.followingCount(${user.followingCount})}">0
|
||||
Following</p>
|
||||
<p th:if="user.followersCount != null" th:text="#{user-by-id.followersCount(${user.followersCount})}">0
|
||||
Followers</p>
|
||||
</div>
|
||||
<div th:replace="~{fragments-timeline :: simple-timline(${userTimeline},'/users/'+${user.name}+'@'+${user.host})}"></div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -16,9 +16,8 @@
|
|||
|
||||
package dev.usbharu.hideout.mastodon.infrastructure.exposedquery
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||
import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji
|
||||
import dev.usbharu.hideout.core.domain.model.media.*
|
||||
import dev.usbharu.hideout.core.domain.model.media.FileType
|
||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.*
|
||||
|
@ -30,7 +29,6 @@ import dev.usbharu.hideout.mastodon.query.StatusQuery
|
|||
import dev.usbharu.hideout.mastodon.query.StatusQueryService
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.net.URI
|
||||
import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia
|
||||
import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.CustomEmoji as MastodonEmoji
|
||||
|
||||
|
@ -274,40 +272,6 @@ private fun toStatus(it: ResultRow, queryAlias: QueryAlias, inReplyToAlias: Alia
|
|||
editedAt = null
|
||||
)
|
||||
|
||||
fun ResultRow.toMedia(): EntityMedia {
|
||||
val fileType = FileType.valueOf(this[Media.type])
|
||||
val mimeType = this[Media.mimeType]
|
||||
return EntityMedia(
|
||||
id = MediaId(this[Media.id]),
|
||||
name = MediaName(this[Media.name]),
|
||||
url = URI.create(this[Media.url]),
|
||||
remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) },
|
||||
thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) },
|
||||
type = fileType,
|
||||
blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) },
|
||||
mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType),
|
||||
description = this[Media.description]?.let { MediaDescription(it) },
|
||||
actorId = ActorId(this[Media.actorId])
|
||||
)
|
||||
}
|
||||
|
||||
fun ResultRow.toMediaOrNull(): EntityMedia? {
|
||||
val fileType = FileType.valueOf(this.getOrNull(Media.type) ?: return null)
|
||||
val mimeType = this.getOrNull(Media.mimeType) ?: return null
|
||||
return EntityMedia(
|
||||
id = MediaId(this.getOrNull(Media.id) ?: return null),
|
||||
name = MediaName(this.getOrNull(Media.name) ?: return null),
|
||||
url = URI.create(this.getOrNull(Media.url) ?: return null),
|
||||
remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) },
|
||||
thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) },
|
||||
type = FileType.valueOf(this[Media.type]),
|
||||
blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) },
|
||||
mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType),
|
||||
description = this[Media.description]?.let { MediaDescription(it) },
|
||||
actorId = ActorId(this[Media.actorId])
|
||||
)
|
||||
}
|
||||
|
||||
fun EntityMedia.toMediaAttachments(): MediaAttachment = MediaAttachment(
|
||||
id = id.id.toString(),
|
||||
type = when (type) {
|
||||
|
|
Loading…
Reference in New Issue