diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/ActorDetail.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/ActorDetail.kt new file mode 100644 index 00000000..d0c552c0 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/ActorDetail.kt @@ -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, + ) + } + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetail.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetail.kt new file mode 100644 index 00000000..8089a404 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetail.kt @@ -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 +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetailApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetailApplicationService.kt new file mode 100644 index 00000000..722f16fe --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/GetActorDetailApplicationService.kt @@ -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( + 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) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/GetLocalInstanceApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/GetLocalInstanceApplicationService.kt index b96b7e06..cdca8b84 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/GetLocalInstanceApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/instance/GetLocalInstanceApplicationService.kt @@ -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!! } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/GetUserTimeline.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/GetUserTimeline.kt new file mode 100644 index 00000000..8ebc7d15 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/GetUserTimeline.kt @@ -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) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/GetUserTimelineApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/GetUserTimelineApplicationService.kt new file mode 100644 index 00000000..0d85511c --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/timeline/GetUserTimelineApplicationService.kt @@ -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>(transaction, logger) { + override suspend fun internalExecute( + command: GetUserTimeline, + principal: Principal + ): PaginationList { + 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) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/MessageSourceConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/MessageSourceConfig.kt new file mode 100644 index 00000000..dcfecd4b --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/MessageSourceConfig.kt @@ -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() +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt index 2eccad56..d0b071ca 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt @@ -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 { if (jwkConfig.keyId == null) { logger.error("hideout.security.jwt.keyId is null.") diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SpringMvcConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SpringMvcConfig.kt index ee75a0ef..301762d4 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SpringMvcConfig.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SpringMvcConfig.kt @@ -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) { resolvers.add(jsonOrFormModelMethodProcessor) } + + override fun addInterceptors(registry: InterceptorRegistry) { + registry.addInterceptor(spaInterceptor) + } } @Configuration diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/acct/Acct.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/acct/Acct.kt index 8be92e3a..148e01ca 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/acct/Acct.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/support/acct/Acct.kt @@ -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('@', "") + ) + } + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailHashedPassword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailHashedPassword.kt index 8e5edf56..2e73d911 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailHashedPassword.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailHashedPassword.kt @@ -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]" } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailId.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailId.kt index 0641cd6e..23faa150 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailId.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/userdetails/UserDetailId.kt @@ -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)" } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt new file mode 100644 index 00000000..82be6f96 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt @@ -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, principal: Principal): List { + 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): 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) + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt index 8dc2184a..9408d01a 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt @@ -211,17 +211,38 @@ class ExposedPostRepository( visibilityList: List, of: Page? ): PaginationList { + 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 ) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt index d5bd5f2a..0f145fbe 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt @@ -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) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/SPAInterceptor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/SPAInterceptor.kt new file mode 100644 index 00000000..b3a6eeb0 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/SPAInterceptor.kt @@ -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" + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/IndexController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/IndexController.kt new file mode 100644 index 00000000..6f181ce4 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/IndexController.kt @@ -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" + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/auth/AuthController.kt similarity index 68% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/auth/AuthController.kt index b19f3c4a..ccda2fab 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/AuthController.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/auth/AuthController.kt @@ -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" } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/SignUpForm.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/auth/SignUpForm.kt similarity index 68% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/SignUpForm.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/auth/SignUpForm.kt index d70eb9c2..320187d8 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/api/auth/SignUpForm.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/auth/SignUpForm.kt @@ -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, diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/common/OGP.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/common/OGP.kt new file mode 100644 index 00000000..e3206294 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/common/OGP.kt @@ -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? +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PublishController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PublishController.kt index 86eb8db7..a3313ba2 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PublishController.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PublishController.kt @@ -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() ) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PublishPost.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PublishPost.kt index c732d2eb..5da5aa30 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PublishPost.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/posts/PublishPost.kt @@ -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 +) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/timeline/TimelineController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/timeline/TimelineController.kt index 52e83f5d..c80b999b 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/timeline/TimelineController.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/timeline/TimelineController.kt @@ -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 { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/user/UserController.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/user/UserController.kt new file mode 100644 index 00000000..c2691652 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/interfaces/web/user/UserController.kt @@ -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" + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/usertimeline/UserTimelineQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/usertimeline/UserTimelineQueryService.kt new file mode 100644 index 00000000..2981cfa7 --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/query/usertimeline/UserTimelineQueryService.kt @@ -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, principal: Principal): List +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt index 4af71576..8f9d0bfd 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt @@ -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) } diff --git a/hideout-core/src/main/resources/application-dev.yml b/hideout-core/src/main/resources/application-dev.yml index a6d4118b..2c19a35e 100644 --- a/hideout-core/src/main/resources/application-dev.yml +++ b/hideout-core/src/main/resources/application-dev.yml @@ -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: diff --git a/hideout-core/src/main/resources/messages/hideout-web-messages.properties b/hideout-core/src/main/resources/messages/hideout-web-messages.properties index c22c2992..d2c0de78 100644 --- a/hideout-core/src/main/resources/messages/hideout-web-messages.properties +++ b/hideout-core/src/main/resources/messages/hideout-web-messages.properties @@ -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 \ No newline at end of file +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} \ No newline at end of file diff --git a/hideout-core/src/main/resources/messages/hideout-web-messages_en_US.properties b/hideout-core/src/main/resources/messages/hideout-web-messages_en_US.properties index f32efecd..e19ac2b5 100644 --- a/hideout-core/src/main/resources/messages/hideout-web-messages_en_US.properties +++ b/hideout-core/src/main/resources/messages/hideout-web-messages_en_US.properties @@ -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} \ No newline at end of file +post.repost-by=Repost by {0} +user-by-id.followersCount={0} Followers +user-by-id.followingCount={0} Following +user-by-id.postsCount={0} Posts \ No newline at end of file diff --git a/hideout-core/src/main/resources/messages/hideout-web-messages_ja_JP.properties b/hideout-core/src/main/resources/messages/hideout-web-messages_ja_JP.properties index c22c2992..d2c0de78 100644 --- a/hideout-core/src/main/resources/messages/hideout-web-messages_ja_JP.properties +++ b/hideout-core/src/main/resources/messages/hideout-web-messages_ja_JP.properties @@ -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 \ No newline at end of file +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} \ No newline at end of file diff --git a/hideout-core/src/main/resources/templates/fragments-post.html b/hideout-core/src/main/resources/templates/fragments-post.html index 5b9533b6..e55f55be 100644 --- a/hideout-core/src/main/resources/templates/fragments-post.html +++ b/hideout-core/src/main/resources/templates/fragments-post.html @@ -11,7 +11,8 @@
-

+
@@ -43,6 +44,7 @@
+ Reply diff --git a/hideout-core/src/main/resources/templates/fragments-timeline.html b/hideout-core/src/main/resources/templates/fragments-timeline.html index f08d6715..ef29250c 100644 --- a/hideout-core/src/main/resources/templates/fragments-timeline.html +++ b/hideout-core/src/main/resources/templates/fragments-timeline.html @@ -8,7 +8,7 @@
@@ -16,7 +16,7 @@
diff --git a/hideout-core/src/main/resources/templates/homeTimeline.html b/hideout-core/src/main/resources/templates/homeTimeline.html index 60bc762b..c1f982ac 100644 --- a/hideout-core/src/main/resources/templates/homeTimeline.html +++ b/hideout-core/src/main/resources/templates/homeTimeline.html @@ -1,12 +1,10 @@ - + Title - +
\ No newline at end of file diff --git a/hideout-core/src/main/resources/templates/index.html b/hideout-core/src/main/resources/templates/index.html new file mode 100644 index 00000000..06321a91 --- /dev/null +++ b/hideout-core/src/main/resources/templates/index.html @@ -0,0 +1,21 @@ + + + + + Index + + + + + + + + + + + + \ No newline at end of file diff --git a/hideout-core/src/main/resources/templates/layout.html b/hideout-core/src/main/resources/templates/layout.html new file mode 100644 index 00000000..c0f4c0b4 --- /dev/null +++ b/hideout-core/src/main/resources/templates/layout.html @@ -0,0 +1,27 @@ + + + + + + Index + + + + + + + + +
+ HideoutNew Post +
+
+
+
+
+
+ +
+ + \ No newline at end of file diff --git a/hideout-core/src/main/resources/templates/post-postForm.html b/hideout-core/src/main/resources/templates/post-postForm.html index a53bfba4..1142d823 100644 --- a/hideout-core/src/main/resources/templates/post-postForm.html +++ b/hideout-core/src/main/resources/templates/post-postForm.html @@ -1,5 +1,5 @@ - + New Posts! @@ -7,8 +7,8 @@ +
-
- +
\ No newline at end of file diff --git a/hideout-core/src/main/resources/templates/postById.html b/hideout-core/src/main/resources/templates/postById.html index bbf5e358..7f1a3c3d 100644 --- a/hideout-core/src/main/resources/templates/postById.html +++ b/hideout-core/src/main/resources/templates/postById.html @@ -1,24 +1,13 @@ - + - Posts - hideout - - - - - - - - - - + Posts - hideout -