mirror of https://github.com/usbharu/Hideout.git
feat: 投稿フォームを追加
This commit is contained in:
parent
1fc6a1fa38
commit
91573c0b2e
|
@ -1,7 +1,6 @@
|
||||||
package dev.usbharu.hideout.core.application.post
|
package dev.usbharu.hideout.core.application.post
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.application.exception.InternalServerException
|
import dev.usbharu.hideout.core.application.exception.InternalServerException
|
||||||
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
|
|
||||||
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
||||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||||
|
@ -32,7 +31,7 @@ class GetPostDetailApplicationService(
|
||||||
val post = postRepository.findById(PostId(command.postId))
|
val post = postRepository.findById(PostId(command.postId))
|
||||||
?: throw IllegalArgumentException("Post ${command.postId} not found.")
|
?: throw IllegalArgumentException("Post ${command.postId} not found.")
|
||||||
if (iPostReadAccessControl.isAllow(post, principal).not()) {
|
if (iPostReadAccessControl.isAllow(post, principal).not()) {
|
||||||
throw PermissionDeniedException()
|
throw IllegalArgumentException("Post ${command.postId} not found.")
|
||||||
}
|
}
|
||||||
val actor =
|
val actor =
|
||||||
actorRepository.findById(post.actorId) ?: throw InternalServerException("Actor ${post.actorId} not found.")
|
actorRepository.findById(post.actorId) ?: throw InternalServerException("Actor ${post.actorId} not found.")
|
||||||
|
@ -65,7 +64,7 @@ class GetPostDetailApplicationService(
|
||||||
val post = postRepository.findById(postId) ?: return null
|
val post = postRepository.findById(postId) ?: return null
|
||||||
|
|
||||||
if (iPostReadAccessControl.isAllow(post, principal).not()) {
|
if (iPostReadAccessControl.isAllow(post, principal).not()) {
|
||||||
throw PermissionDeniedException()
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val (first, second: Instance, third) = if (actor.id != post.actorId) {
|
val (first, second: Instance, third) = if (actor.id != post.actorId) {
|
||||||
|
|
|
@ -83,6 +83,8 @@ class SecurityConfig {
|
||||||
authorize(POST, "/auth/sign_up", permitAll)
|
authorize(POST, "/auth/sign_up", permitAll)
|
||||||
authorize(GET, "/users/{username}/posts/{postId}", permitAll)
|
authorize(GET, "/users/{username}/posts/{postId}", permitAll)
|
||||||
authorize(GET, "/files/*", permitAll)
|
authorize(GET, "/files/*", permitAll)
|
||||||
|
authorize(POST, "/publish", authenticated)
|
||||||
|
authorize(GET, "/publish", authenticated)
|
||||||
|
|
||||||
authorize(anyRequest, authenticated)
|
authorize(anyRequest, authenticated)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,12 @@ interface IPostReadAccessControl {
|
||||||
class DefaultPostReadAccessControl(private val relationshipRepository: RelationshipRepository) :
|
class DefaultPostReadAccessControl(private val relationshipRepository: RelationshipRepository) :
|
||||||
IPostReadAccessControl {
|
IPostReadAccessControl {
|
||||||
override suspend fun isAllow(post: Post, principal: Principal): Boolean {
|
override suspend fun isAllow(post: Post, principal: Principal): Boolean {
|
||||||
|
|
||||||
|
//ポスト主は無条件で見れる
|
||||||
|
if (post.actorId == principal.actorId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
val relationship = (relationshipRepository.findByActorIdAndTargetId(post.actorId, principal.actorId)
|
val relationship = (relationshipRepository.findByActorIdAndTargetId(post.actorId, principal.actorId)
|
||||||
?: Relationship.default(post.actorId, principal.actorId))
|
?: Relationship.default(post.actorId, principal.actorId))
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package dev.usbharu.hideout.core.interfaces.web.posts
|
package dev.usbharu.hideout.core.interfaces.web.posts
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
|
||||||
import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService
|
import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService
|
||||||
import dev.usbharu.hideout.core.application.post.GetPostDetail
|
import dev.usbharu.hideout.core.application.post.GetPostDetail
|
||||||
import dev.usbharu.hideout.core.application.post.GetPostDetailApplicationService
|
import dev.usbharu.hideout.core.application.post.GetPostDetailApplicationService
|
||||||
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder
|
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder
|
||||||
|
import org.springframework.security.access.AccessDeniedException
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
import org.springframework.ui.Model
|
import org.springframework.ui.Model
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
@ -18,10 +20,15 @@ class PostsController(
|
||||||
@GetMapping("/users/{name}/posts/{id}")
|
@GetMapping("/users/{name}/posts/{id}")
|
||||||
suspend fun postById(@PathVariable id: Long, model: Model): String {
|
suspend fun postById(@PathVariable id: Long, model: Model): String {
|
||||||
val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
||||||
val post = getPostDetailApplicationService.execute(GetPostDetail(id), principal)
|
try {
|
||||||
val instance = getLocalInstanceApplicationService.execute(Unit, principal)
|
val post = getPostDetailApplicationService.execute(GetPostDetail(id), principal)
|
||||||
model.addAttribute("post", post)
|
val instance = getLocalInstanceApplicationService.execute(Unit, principal)
|
||||||
model.addAttribute("instance", instance)
|
model.addAttribute("post", post)
|
||||||
|
model.addAttribute("instance", instance)
|
||||||
|
} catch (e: PermissionDeniedException) {
|
||||||
|
throw AccessDeniedException("403 Forbidden", e)
|
||||||
|
}
|
||||||
|
|
||||||
return "postById"
|
return "postById"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,17 +3,23 @@ package dev.usbharu.hideout.core.interfaces.web.posts
|
||||||
import dev.usbharu.hideout.core.application.actor.GetUserDetail
|
import dev.usbharu.hideout.core.application.actor.GetUserDetail
|
||||||
import dev.usbharu.hideout.core.application.actor.GetUserDetailApplicationService
|
import dev.usbharu.hideout.core.application.actor.GetUserDetailApplicationService
|
||||||
import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService
|
import dev.usbharu.hideout.core.application.instance.GetLocalInstanceApplicationService
|
||||||
|
import dev.usbharu.hideout.core.application.post.RegisterLocalPost
|
||||||
|
import dev.usbharu.hideout.core.application.post.RegisterLocalPostApplicationService
|
||||||
|
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||||
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder
|
import dev.usbharu.hideout.core.infrastructure.springframework.SpringSecurityFormLoginPrincipalContextHolder
|
||||||
import org.springframework.security.access.AccessDeniedException
|
import org.springframework.security.access.AccessDeniedException
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
import org.springframework.ui.Model
|
import org.springframework.ui.Model
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
class PublishController(
|
class PublishController(
|
||||||
private val getLocalInstanceApplicationService: GetLocalInstanceApplicationService,
|
private val getLocalInstanceApplicationService: GetLocalInstanceApplicationService,
|
||||||
private val springSecurityFormLoginPrincipalContextHolder: SpringSecurityFormLoginPrincipalContextHolder,
|
private val springSecurityFormLoginPrincipalContextHolder: SpringSecurityFormLoginPrincipalContextHolder,
|
||||||
private val getUserDetailApplicationService: GetUserDetailApplicationService
|
private val getUserDetailApplicationService: GetUserDetailApplicationService,
|
||||||
|
private val userRegisterLocalPostApplicationService: RegisterLocalPostApplicationService
|
||||||
) {
|
) {
|
||||||
@GetMapping("/publish")
|
@GetMapping("/publish")
|
||||||
suspend fun publish(model: Model): String {
|
suspend fun publish(model: Model): String {
|
||||||
|
@ -26,7 +32,29 @@ class PublishController(
|
||||||
val instance = getLocalInstanceApplicationService.execute(Unit, principal)
|
val instance = getLocalInstanceApplicationService.execute(Unit, principal)
|
||||||
val userDetail = getUserDetailApplicationService.execute(GetUserDetail(principal.userDetailId!!.id), principal)
|
val userDetail = getUserDetailApplicationService.execute(GetUserDetail(principal.userDetailId!!.id), principal)
|
||||||
model.addAttribute("instance", instance)
|
model.addAttribute("instance", instance)
|
||||||
model.addAttribute("user")
|
model.addAttribute("user", userDetail)
|
||||||
|
model.addAttribute("form", PublishPost())
|
||||||
return "post-postForm"
|
return "post-postForm"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/publish")
|
||||||
|
suspend fun publishForm(@ModelAttribute publishPost: PublishPost): String {
|
||||||
|
val principal = springSecurityFormLoginPrincipalContextHolder.getPrincipal()
|
||||||
|
if (principal.userDetailId == null) {
|
||||||
|
throw AccessDeniedException("403 Forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
val command = RegisterLocalPost(
|
||||||
|
content = publishPost.status.orEmpty(),
|
||||||
|
overview = publishPost.overview,
|
||||||
|
visibility = Visibility.valueOf(publishPost.visibility.uppercase()),
|
||||||
|
repostId = null,
|
||||||
|
replyId = null,
|
||||||
|
sensitive = false,
|
||||||
|
mediaIds = emptyList()
|
||||||
|
)
|
||||||
|
val id = userRegisterLocalPostApplicationService.execute(command, principal)
|
||||||
|
|
||||||
|
return "redirect:/users/${principal.acct?.userpart}/posts/${id}"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package dev.usbharu.hideout.core.interfaces.web.posts
|
||||||
|
|
||||||
|
data class PublishPost(var status: String? = null, var overview: String? = null, var visibility: String = "PUBLIC")
|
|
@ -5,7 +5,15 @@ common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
||||||
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
||||||
common.video=\u52D5\u753B
|
common.video=\u52D5\u753B
|
||||||
common.video-download-link=\u52D5\u753B\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306F\u30B5\u30E0\u30CD\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
common.video-download-link=\u52D5\u753B\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306F\u30B5\u30E0\u30CD\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
||||||
|
common.visibility=\u516C\u958B\u7BC4\u56F2
|
||||||
|
common.visibility-followers=\u30D5\u30A9\u30ED\u30EF\u30FC\u306E\u307F
|
||||||
|
common.visibility-public=\u30D1\u30D6\u30EA\u30C3\u30AF
|
||||||
|
common.visibility-unlisted=\u672A\u53CE\u8F09
|
||||||
post-by-id.title={0} \u3055\u3093\u306E\u6295\u7A3F - {1}
|
post-by-id.title={0} \u3055\u3093\u306E\u6295\u7A3F - {1}
|
||||||
post-form.new-posts=\u65B0\u3057\u3044\u6295\u7A3F!
|
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-submit=\u6295\u7A3F\u3059\u308B
|
||||||
post.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
|
|
@ -5,6 +5,14 @@ common.thumbnail=thumbnail
|
||||||
common.unknwon-file-type=Unknown filetype
|
common.unknwon-file-type=Unknown filetype
|
||||||
common.video=Video
|
common.video=Video
|
||||||
common.video-download-link=Download the Video or thumbnail.
|
common.video-download-link=Download the Video or thumbnail.
|
||||||
|
common.visibility=Visibility
|
||||||
|
common.visibility-followers=Followers only
|
||||||
|
common.visibility-public=Public
|
||||||
|
common.visibility-unlisted=Unlisted
|
||||||
post-form.new-posts=New Posts!
|
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-submit=Submit!
|
||||||
post.repost=Repost
|
post.repost=Repost
|
||||||
post.repost-by=Repost by {0}
|
post.repost-by=Repost by {0}
|
|
@ -5,7 +5,15 @@ common.thumbnail=\u30B5\u30E0\u30CD\u30A4\u30EB
|
||||||
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
common.unknwon-file-type=\u4E0D\u660E\u306A\u30D5\u30A1\u30A4\u30EB\u5F62\u5F0F
|
||||||
common.video=\u52D5\u753B
|
common.video=\u52D5\u753B
|
||||||
common.video-download-link=\u52D5\u753B\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306F\u30B5\u30E0\u30CD\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
common.video-download-link=\u52D5\u753B\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306F\u30B5\u30E0\u30CD\u30A4\u30EB\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9
|
||||||
|
common.visibility=\u516C\u958B\u7BC4\u56F2
|
||||||
|
common.visibility-followers=\u30D5\u30A9\u30ED\u30EF\u30FC\u306E\u307F
|
||||||
|
common.visibility-public=\u30D1\u30D6\u30EA\u30C3\u30AF
|
||||||
|
common.visibility-unlisted=\u672A\u53CE\u8F09
|
||||||
post-by-id.title={0} \u3055\u3093\u306E\u6295\u7A3F - {1}
|
post-by-id.title={0} \u3055\u3093\u306E\u6295\u7A3F - {1}
|
||||||
post-form.new-posts=\u65B0\u3057\u3044\u6295\u7A3F!
|
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-submit=\u6295\u7A3F\u3059\u308B
|
||||||
post.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
|
|
@ -1,10 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Title</title>
|
<title>Title</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div th:fragment="single-simple-actor(actor)"></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -8,5 +8,44 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<div>
|
||||||
|
<img alt="" height="80px" src="" th:src="${user.iconUrl}" width="80px">
|
||||||
|
<div style="display: inline-block">
|
||||||
|
<p th:text="${user.screenName}+'('+${user.name}+'@'+${user.domain}+')'"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form action="/publish" method="post" th:action="@{/publish}" th:object="${form}">
|
||||||
|
<div>
|
||||||
|
<label for="stats-form-overview" th:text="#{post-form.new-posts-cw}"
|
||||||
|
th:title="#{post-form.new-posts-cw-title}"
|
||||||
|
title="Add content warning">CW</label>
|
||||||
|
<input id="stats-form-overview" name="overview" type="text">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="status-form" th:text="#{post-form.new-posts-form-label}">What's on your mind?</label>
|
||||||
|
<br>
|
||||||
|
<textarea cols="33" id="status-form" name="status" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<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"
|
||||||
|
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"
|
||||||
|
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"
|
||||||
|
value="FOLLOWERS">
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input th:value="#{post-form.new-posts-submit}" type="submit" value="Publish!">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue