mirror of https://github.com/usbharu/Hideout.git
Merge pull request #246 from usbharu/feature/post-decoration
Feature/post decoration
This commit is contained in:
commit
99081d6483
|
@ -215,6 +215,8 @@ dependencies {
|
|||
implementation("org.flywaydb:flyway-core")
|
||||
|
||||
implementation("dev.usbharu:emoji-kt:2.0.0")
|
||||
implementation("org.jsoup:jsoup:1.17.2")
|
||||
implementation("com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20220608.1")
|
||||
|
||||
implementation("io.ktor:ktor-client-logging-jvm:$ktor_version")
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ insert into relationships (actor_id, target_actor_id, following, blocking, mutin
|
|||
ignore_follow_request)
|
||||
VALUES (9, 8, true, false, false, false, false);
|
||||
|
||||
insert into POSTS (ID, ACTOR_ID, OVERVIEW, TEXT, CREATED_AT, VISIBILITY, URL, REPLY_ID, REPOST_ID, SENSITIVE, AP_ID)
|
||||
VALUES (1239, 8, null, 'test post', 12345680, 2, 'https://example.com/users/test-user8/posts/1239', null, null, false,
|
||||
insert into POSTS (ID, ACTOR_ID, OVERVIEW, CONTENT, TEXT, CREATED_AT, VISIBILITY, URL, REPLY_ID, REPOST_ID, SENSITIVE,
|
||||
AP_ID)
|
||||
VALUES (1239, 8, null, '<p>test post</p>', 'test post', 12345680, 2, 'https://example.com/users/test-user8/posts/1239',
|
||||
null, null, false,
|
||||
'https://example.com/users/test-user8/posts/1239');
|
||||
|
|
|
@ -21,7 +21,9 @@ insert into relationships (actor_id, target_actor_id, following, blocking, mutin
|
|||
ignore_follow_request)
|
||||
VALUES (5, 4, true, false, false, false, false);
|
||||
|
||||
insert into POSTS (ID, "actor_id", OVERVIEW, TEXT, "created_at", VISIBILITY, URL, "repost_id", "reply_id", SENSITIVE,
|
||||
insert into POSTS (ID, "actor_id", OVERVIEW, CONTENT, TEXT, "created_at", VISIBILITY, URL, "repost_id", "reply_id",
|
||||
SENSITIVE,
|
||||
AP_ID)
|
||||
VALUES (1237, 4, null, 'test post', 12345680, 0, 'https://example.com/users/test-user4/posts/1237', null, null, false,
|
||||
VALUES (1237, 4, null, '<p>test post</p>', 'test post', 12345680, 0, 'https://example.com/users/test-user4/posts/1237',
|
||||
null, null, false,
|
||||
'https://example.com/users/test-user4/posts/1237');
|
||||
|
|
|
@ -21,7 +21,9 @@ insert into relationships (actor_id, target_actor_id, following, blocking, mutin
|
|||
ignore_follow_request)
|
||||
VALUES (7, 6, true, false, false, false, false);
|
||||
|
||||
insert into POSTS (ID, "actor_ID", OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE,
|
||||
insert into POSTS (ID, "actor_ID", OVERVIEW, CONTENT, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID",
|
||||
SENSITIVE,
|
||||
AP_ID)
|
||||
VALUES (1238, 6, null, 'test post', 12345680, 1, 'https://example.com/users/test-user6/posts/1238', null, null, false,
|
||||
VALUES (1238, 6, null, '<p>test post</p>', 'test post', 12345680, 1, 'https://example.com/users/test-user6/posts/1238',
|
||||
null, null, false,
|
||||
'https://example.com/users/test-user6/posts/1238');
|
||||
|
|
|
@ -9,9 +9,11 @@ VALUES (11, 'test-user11', 'example.com', 'Im test-user11.', 'THis account is te
|
|||
'https://example.com/users/test-user11#pubkey', 'https://example.com/users/test-user11/following',
|
||||
'https://example.com/users/test-user11/followers', null, false, 0, 0, 0, null);
|
||||
|
||||
insert into POSTS (id, actor_id, overview, text, created_at, visibility, url, repost_id, reply_id, sensitive, ap_id,
|
||||
insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive,
|
||||
ap_id,
|
||||
deleted)
|
||||
VALUES (1242, 11, null, 'test post', 12345680, 0, 'https://example.com/users/test-user11/posts/1242', null, null, false,
|
||||
VALUES (1242, 11, null, '<p>test post</p>', 'test post', 12345680, 0,
|
||||
'https://example.com/users/test-user11/posts/1242', null, null, false,
|
||||
'https://example.com/users/test-user11/posts/1242', false);
|
||||
|
||||
insert into MEDIA (ID, NAME, URL, REMOTE_URL, THUMBNAIL_URL, TYPE, BLURHASH, MIME_TYPE, DESCRIPTION)
|
||||
|
|
|
@ -9,9 +9,12 @@ VALUES (10, 'test-user10', 'example.com', 'Im test-user10.', 'THis account is te
|
|||
'https://example.com/users/test-user10#pubkey', 'https://example.com/users/test-user10/following',
|
||||
'https://example.com/users/test-user10/followers', null, false, 0, 0, 0, null);
|
||||
|
||||
insert into POSTS (id, actor_id, overview, text, created_at, visibility, url, repost_id, reply_id, sensitive, ap_id,
|
||||
insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive,
|
||||
ap_id,
|
||||
deleted)
|
||||
VALUES (1240, 10, null, 'test post', 12345680, 0, 'https://example.com/users/test-user10/posts/1240', null, null, false,
|
||||
VALUES (1240, 10, null, '<p>test post</p>', 'test post', 12345680, 0,
|
||||
'https://example.com/users/test-user10/posts/1240', null, null, false,
|
||||
'https://example.com/users/test-user10/posts/1240', false),
|
||||
(1241, 10, null, 'test post', 12345680, 0, 'https://example.com/users/test-user10/posts/1241', null, 1240, false,
|
||||
(1241, 10, null, '<p>test post</p>', 'test post', 12345680, 0,
|
||||
'https://example.com/users/test-user10/posts/1241', null, 1240, false,
|
||||
'https://example.com/users/test-user10/posts/1241', false);
|
||||
|
|
|
@ -9,7 +9,9 @@ VALUES (3, 'test-user3', 'example.com', 'Im test user3.', 'THis account is test
|
|||
'https://example.com/users/test-user3#pubkey', 'https://example.com/users/test-user3/following',
|
||||
'https://example.com/users/test-user3/followers', null, false, 0, 0, 0, null);
|
||||
|
||||
insert into POSTS (id, actor_id, overview, text, created_at, visibility, url, repost_id, reply_id, sensitive, ap_id,
|
||||
insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive,
|
||||
ap_id,
|
||||
deleted)
|
||||
VALUES (1236, 3, null, 'test post', 12345680, 2, 'https://example.com/users/test-user3/posts/1236', null, null, false,
|
||||
VALUES (1236, 3, null, '<p>test post</p>', 'test post', 12345680, 2, 'https://example.com/users/test-user3/posts/1236',
|
||||
null, null, false,
|
||||
'https://example.com/users/test-user3/posts/1236', false)
|
||||
|
|
|
@ -9,7 +9,9 @@ VALUES (1, 'test-user', 'example.com', 'Im test user.', 'THis account is test us
|
|||
'https://example.com/users/test-user#pubkey', 'https://example.com/users/test-user/following',
|
||||
'https://example.com/users/test-users/followers', null, false, 0, 0, 0, null);
|
||||
|
||||
insert into POSTS (id, actor_id, overview, text, created_at, visibility, url, repost_id, reply_id, sensitive, ap_id,
|
||||
insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive,
|
||||
ap_id,
|
||||
deleted)
|
||||
VALUES (1234, 1, null, 'test post', 12345680, 0, 'https://example.com/users/test-user/posts/1234', null, null, false,
|
||||
VALUES (1234, 1, null, '<p>test post</p>', 'test post', 12345680, 0, 'https://example.com/users/test-user/posts/1234',
|
||||
null, null, false,
|
||||
'https://example.com/users/test-user/posts/1234', false)
|
||||
|
|
|
@ -9,7 +9,9 @@ VALUES (2, 'test-user2', 'example.com', 'Im test user2.', 'THis account is test
|
|||
'https://example.com/users/test-user2#pubkey', 'https://example.com/users/test-user2/following',
|
||||
'https://example.com/users/test-user2/followers', null, false, 0, 0, 0, null);
|
||||
|
||||
insert into POSTS (id, actor_id, overview, text, created_at, visibility, url, repost_id, reply_id, sensitive, ap_id,
|
||||
insert into POSTS (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive,
|
||||
ap_id,
|
||||
deleted)
|
||||
VALUES (1235, 2, null, 'test post', 12345680, 1, 'https://example.com/users/test-user2/posts/1235', null, null, false,
|
||||
VALUES (1235, 2, null, '<p>test post</p>', 'test post', 12345680, 1, 'https://example.com/users/test-user2/posts/1235',
|
||||
null, null, false,
|
||||
'https://example.com/users/test-user2/posts/1235', false)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
insert into posts (id, actor_id, overview, text, created_at, visibility, url, repost_id, reply_id, sensitive, ap_id)
|
||||
VALUES (1, 1, null, 'hello', 1234455, 0, 'https://localhost/users/1/posts/1', null, null, false,
|
||||
insert into posts (id, actor_id, overview, content, text, created_at, visibility, url, repost_id, reply_id, sensitive,
|
||||
ap_id)
|
||||
VALUES (1, 1, null, '<p>test post</p>', 'hello', 1234455, 0, 'https://localhost/users/1/posts/1', null, null, false,
|
||||
'https://users/1/posts/1');
|
||||
|
|
|
@ -128,7 +128,7 @@ class APNoteServiceImpl(
|
|||
postBuilder.of(
|
||||
id = postRepository.generateId(),
|
||||
actorId = person.second.id,
|
||||
text = note.content,
|
||||
content = note.content,
|
||||
createdAt = Instant.parse(note.published).toEpochMilli(),
|
||||
visibility = visibility,
|
||||
url = note.id,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package dev.usbharu.hideout.application.config
|
||||
|
||||
import org.owasp.html.HtmlPolicyBuilder
|
||||
import org.owasp.html.PolicyFactory
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
class HtmlSanitizeConfig {
|
||||
@Bean
|
||||
fun policy(): PolicyFactory {
|
||||
return HtmlPolicyBuilder()
|
||||
.allowElements("p")
|
||||
.allowElements("a")
|
||||
.allowElements("br")
|
||||
.allowAttributes("href").onElements("a")
|
||||
.allowUrlProtocols("http", "https")
|
||||
.allowElements({ _, _ -> return@allowElements "p" }, "h1", "h2", "h3", "h4", "h5", "h6")
|
||||
.toFactory()
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package dev.usbharu.hideout.core.domain.model.post
|
||||
|
||||
import dev.usbharu.hideout.application.config.CharacterLimit
|
||||
import dev.usbharu.hideout.core.service.post.PostContentFormatter
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.Instant
|
||||
|
||||
|
@ -8,6 +9,7 @@ data class Post private constructor(
|
|||
val id: Long,
|
||||
val actorId: Long,
|
||||
val overview: String? = null,
|
||||
val content: String,
|
||||
val text: String,
|
||||
val createdAt: Long,
|
||||
val visibility: Visibility,
|
||||
|
@ -22,13 +24,16 @@ data class Post private constructor(
|
|||
) {
|
||||
|
||||
@Component
|
||||
class PostBuilder(private val characterLimit: CharacterLimit) {
|
||||
class PostBuilder(
|
||||
private val characterLimit: CharacterLimit,
|
||||
private val postContentFormatter: PostContentFormatter
|
||||
) {
|
||||
@Suppress("FunctionMinLength", "LongParameterList")
|
||||
fun of(
|
||||
id: Long,
|
||||
actorId: Long,
|
||||
overview: String? = null,
|
||||
text: String,
|
||||
content: String,
|
||||
createdAt: Long,
|
||||
visibility: Visibility,
|
||||
url: String,
|
||||
|
@ -49,12 +54,14 @@ data class Post private constructor(
|
|||
overview
|
||||
}
|
||||
|
||||
val limitedText = if (text.length >= characterLimit.post.text) {
|
||||
text.substring(0, characterLimit.post.text)
|
||||
val limitedText = if (content.length >= characterLimit.post.text) {
|
||||
content.substring(0, characterLimit.post.text)
|
||||
} else {
|
||||
text
|
||||
content
|
||||
}
|
||||
|
||||
val (html, content1) = postContentFormatter.format(limitedText)
|
||||
|
||||
require(url.isNotBlank()) { "url must contain non-blank characters" }
|
||||
require(url.length <= characterLimit.general.url) {
|
||||
"url must not exceed ${characterLimit.general.url} characters."
|
||||
|
@ -67,7 +74,8 @@ data class Post private constructor(
|
|||
id = id,
|
||||
actorId = actorId,
|
||||
overview = limitedOverview,
|
||||
text = limitedText,
|
||||
content = html,
|
||||
text = content1,
|
||||
createdAt = createdAt,
|
||||
visibility = visibility,
|
||||
url = url,
|
||||
|
@ -94,6 +102,7 @@ data class Post private constructor(
|
|||
id = id,
|
||||
actorId = 0,
|
||||
overview = null,
|
||||
content = "",
|
||||
text = "",
|
||||
createdAt = Instant.EPOCH.toEpochMilli(),
|
||||
visibility = visibility,
|
||||
|
@ -113,6 +122,7 @@ data class Post private constructor(
|
|||
id = this.id,
|
||||
actorId = 0,
|
||||
overview = null,
|
||||
content = "",
|
||||
text = "",
|
||||
createdAt = Instant.EPOCH.toEpochMilli(),
|
||||
visibility = visibility,
|
||||
|
|
|
@ -25,7 +25,7 @@ class PostResultRowMapper(private val postBuilder: Post.PostBuilder) : ResultRow
|
|||
id = resultRow[Posts.id],
|
||||
actorId = resultRow[Posts.actorId],
|
||||
overview = resultRow[Posts.overview],
|
||||
text = resultRow[Posts.text],
|
||||
content = resultRow[Posts.text],
|
||||
createdAt = resultRow[Posts.createdAt],
|
||||
visibility = Visibility.values().first { visibility -> visibility.ordinal == resultRow[Posts.visibility] },
|
||||
url = resultRow[Posts.url],
|
||||
|
|
|
@ -27,6 +27,7 @@ class PostRepositoryImpl(
|
|||
it[id] = post.id
|
||||
it[actorId] = post.actorId
|
||||
it[overview] = post.overview
|
||||
it[content] = post.content
|
||||
it[text] = post.text
|
||||
it[createdAt] = post.createdAt
|
||||
it[visibility] = post.visibility.ordinal
|
||||
|
@ -63,6 +64,7 @@ class PostRepositoryImpl(
|
|||
Posts.update({ Posts.id eq post.id }) {
|
||||
it[actorId] = post.actorId
|
||||
it[overview] = post.overview
|
||||
it[content] = post.content
|
||||
it[text] = post.text
|
||||
it[createdAt] = post.createdAt
|
||||
it[visibility] = post.visibility.ordinal
|
||||
|
@ -128,6 +130,7 @@ object Posts : Table() {
|
|||
val id: Column<Long> = long("id")
|
||||
val actorId: Column<Long> = long("actor_id").references(Actors.id)
|
||||
val overview: Column<String?> = varchar("overview", 100).nullable()
|
||||
val content = varchar("content", 5000)
|
||||
val text: Column<String> = varchar("text", 3000)
|
||||
val createdAt: Column<Long> = long("created_at")
|
||||
val visibility: Column<Int> = integer("visibility").default(0)
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package dev.usbharu.hideout.core.service.post
|
||||
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.TextNode
|
||||
import org.jsoup.select.Elements
|
||||
import org.owasp.html.PolicyFactory
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : PostContentFormatter {
|
||||
override fun format(content: String): FormattedPostContent {
|
||||
// まず不正なHTMLを整形する
|
||||
val document = Jsoup.parseBodyFragment(content)
|
||||
val outputSettings = Document.OutputSettings()
|
||||
outputSettings.prettyPrint(false)
|
||||
|
||||
document.outputSettings(outputSettings)
|
||||
|
||||
val unsafeElement = document.getElementsByTag("body").first() ?: return FormattedPostContent(
|
||||
"",
|
||||
""
|
||||
)
|
||||
|
||||
// 文字だけのHTMLなどはここでpタグで囲む
|
||||
val flattenHtml = unsafeElement.childNodes().mapNotNull {
|
||||
if (it is Element) {
|
||||
it
|
||||
} else if (it is TextNode) {
|
||||
Element("p").appendText(it.text())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.filter { it.text().isNotBlank() }
|
||||
|
||||
// HTMLのサニタイズをする
|
||||
val unsafeHtml = Elements(flattenHtml).outerHtml()
|
||||
|
||||
val safeHtml = policyFactory.sanitize(unsafeHtml)
|
||||
|
||||
val safeDocument =
|
||||
Jsoup.parseBodyFragment(safeHtml).getElementsByTag("body").first() ?: return FormattedPostContent("", "")
|
||||
|
||||
val formattedHtml = mutableListOf<Element>()
|
||||
|
||||
// 連続するbrタグを段落に変換する
|
||||
for (element in safeDocument.children()) {
|
||||
var brCount = 0
|
||||
var prevIndex = 0
|
||||
val childNodes = element.childNodes()
|
||||
for ((index, childNode) in childNodes.withIndex()) {
|
||||
if (childNode is Element && childNode.tagName() == "br") {
|
||||
brCount++
|
||||
} else if (brCount >= 2) {
|
||||
formattedHtml.add(Element("p").appendChildren(childNodes.subList(prevIndex, index - brCount)))
|
||||
prevIndex = index
|
||||
}
|
||||
}
|
||||
formattedHtml.add(Element("p").appendChildren(childNodes.subList(prevIndex, childNodes.size)))
|
||||
}
|
||||
|
||||
val elements = Elements(formattedHtml)
|
||||
|
||||
return FormattedPostContent(elements.outerHtml().replace("\n", ""), printHtml(elements))
|
||||
}
|
||||
|
||||
private fun printHtml(element: Elements): String {
|
||||
return element.joinToString("\n\n") {
|
||||
it.childNodes().joinToString("") {
|
||||
if (it is Element && it.tagName() == "br") {
|
||||
"\n"
|
||||
} else if (it is Element) {
|
||||
it.text()
|
||||
} else if (it is TextNode) {
|
||||
it.text()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package dev.usbharu.hideout.core.service.post
|
||||
|
||||
data class FormattedPostContent(
|
||||
val html: String,
|
||||
val content: String
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
package dev.usbharu.hideout.core.service.post
|
||||
|
||||
interface PostContentFormatter {
|
||||
fun format(content: String): FormattedPostContent
|
||||
}
|
|
@ -97,7 +97,7 @@ class PostServiceImpl(
|
|||
id = id,
|
||||
actorId = post.userId,
|
||||
overview = post.overview,
|
||||
text = post.text,
|
||||
content = post.text,
|
||||
createdAt = Instant.now().toEpochMilli(),
|
||||
visibility = post.visibility,
|
||||
url = "${user.url}/posts/$id",
|
||||
|
|
|
@ -90,6 +90,7 @@ create table if not exists posts
|
|||
id bigint primary key,
|
||||
actor_id bigint not null,
|
||||
overview varchar(100) null,
|
||||
content varchar(5000) not null,
|
||||
text varchar(3000) not null,
|
||||
created_at bigint not null,
|
||||
visibility int default 0 not null,
|
||||
|
|
|
@ -12,10 +12,12 @@ import dev.usbharu.hideout.activitypub.service.common.APResourceResolveService
|
|||
import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public
|
||||
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
|
||||
import dev.usbharu.hideout.application.config.CharacterLimit
|
||||
import dev.usbharu.hideout.application.config.HtmlSanitizeConfig
|
||||
import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.service.post.DefaultPostContentFormatter
|
||||
import dev.usbharu.hideout.core.service.post.PostService
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
|
@ -42,7 +44,7 @@ import java.time.Instant
|
|||
|
||||
class APNoteServiceImplTest {
|
||||
|
||||
val postBuilder = Post.PostBuilder(CharacterLimit())
|
||||
val postBuilder = Post.PostBuilder(CharacterLimit(), DefaultPostContentFormatter(HtmlSanitizeConfig().policy()))
|
||||
|
||||
@Test
|
||||
fun `fetchNote(String,String) ノートが既に存在する場合はDBから取得したものを返す`() = runTest {
|
||||
|
@ -71,7 +73,10 @@ class APNoteServiceImplTest {
|
|||
apUserService = mock(),
|
||||
postService = mock(),
|
||||
apResourceResolveService = mock(),
|
||||
postBuilder = Post.PostBuilder(CharacterLimit()),
|
||||
postBuilder = Post.PostBuilder(
|
||||
CharacterLimit(),
|
||||
DefaultPostContentFormatter(HtmlSanitizeConfig().policy())
|
||||
),
|
||||
noteQueryService = noteQueryService,
|
||||
mock(),
|
||||
mock()
|
||||
|
@ -141,7 +146,10 @@ class APNoteServiceImplTest {
|
|||
apUserService = apUserService,
|
||||
postService = mock(),
|
||||
apResourceResolveService = apResourceResolveService,
|
||||
postBuilder = Post.PostBuilder(CharacterLimit()),
|
||||
postBuilder = Post.PostBuilder(
|
||||
CharacterLimit(),
|
||||
DefaultPostContentFormatter(HtmlSanitizeConfig().policy())
|
||||
),
|
||||
noteQueryService = noteQueryService,
|
||||
mock(),
|
||||
mock { }
|
||||
|
@ -190,7 +198,10 @@ class APNoteServiceImplTest {
|
|||
apUserService = mock(),
|
||||
postService = mock(),
|
||||
apResourceResolveService = apResourceResolveService,
|
||||
postBuilder = Post.PostBuilder(CharacterLimit()),
|
||||
postBuilder = Post.PostBuilder(
|
||||
CharacterLimit(),
|
||||
DefaultPostContentFormatter(HtmlSanitizeConfig().policy())
|
||||
),
|
||||
noteQueryService = noteQueryService,
|
||||
mock(),
|
||||
mock()
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package dev.usbharu.hideout.core.service.post
|
||||
|
||||
import dev.usbharu.hideout.application.config.HtmlSanitizeConfig
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DefaultPostContentFormatterTest {
|
||||
val defaultPostContentFormatter = DefaultPostContentFormatter(HtmlSanitizeConfig().policy())
|
||||
|
||||
@Test
|
||||
fun pタグがpタグになる() {
|
||||
//language=HTML
|
||||
val html = """<p>hoge</p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p>hoge</p>", "hoge"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hタグがpタグになる() {
|
||||
//language=HTML
|
||||
val html = """<h1>hoge</h1>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p>hoge</p>", "hoge"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pタグのネストは破棄される() {
|
||||
//language=HTML
|
||||
val html = """<p>hoge<p>fuga</p>piyo</p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p>hoge</p><p>fuga</p><p>piyo</p>", "hoge\n\nfuga\n\npiyo"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun spanタグは無視される() {
|
||||
//language=HTML
|
||||
val html = """<p><span>hoge</span></p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p>hoge</p>", "hoge"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `2連続改行は段落に変換される`() {
|
||||
//language=HTML
|
||||
val html = """<p>hoge<br><br>fuga</p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p>hoge</p><p>fuga</p>", "hoge\n\nfuga"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun iタグは無視される() {
|
||||
//language=HTML
|
||||
val html = """<p><i>hoge</i></p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p>hoge</p>", "hoge"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aタグはhrefの中身のみ引き継がれる() {
|
||||
//language=HTML
|
||||
val html = """<p><a href='https://example.com' class='u-url' target='_blank'>hoge</a></p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p><a href=\"https://example.com\">hoge</a></p>", "hoge"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aタグの中のspanは無視される() {
|
||||
//language=HTML
|
||||
val html = """<p><a href='https://example.com'><span>hoge</span></a></p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p><a href=\"https://example.com\">hoge</a></p>", "hoge"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun brタグのコンテンツは改行になる() {
|
||||
//language=HTML
|
||||
val html = """<p>hoge<br>fuga</p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p>hoge<br> fuga</p>", "hoge\nfuga"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun いきなりテキストが来たらpタグで囲む() {
|
||||
//language=HTML
|
||||
val html = """hoge"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p>hoge</p>", "hoge"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bodyタグが含まれていた場合消す() {
|
||||
//language=HTML
|
||||
val html = """</body><p>hoge</p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(FormattedPostContent("<p>hoge</p>", "hoge"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pタグの中のspanは無視される() {
|
||||
//language=HTML
|
||||
val html =
|
||||
"""<p><span class="h-card" translate="no"><a href="https://test-hideout.usbharu.dev/users/testuser14" class="u-url mention">@<span>testuser14</span></a></span> tes</p>"""
|
||||
|
||||
val actual = defaultPostContentFormatter.format(html)
|
||||
|
||||
assertThat(actual).isEqualTo(
|
||||
FormattedPostContent(
|
||||
"<p><a href=\"https://test-hideout.usbharu.dev/users/testuser14\">@testuser14</a> tes</p>",
|
||||
"@testuser14 tes"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package dev.usbharu.hideout.core.service.post
|
|||
import dev.usbharu.hideout.activitypub.service.activity.create.ApSendCreateService
|
||||
import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService
|
||||
import dev.usbharu.hideout.application.config.CharacterLimit
|
||||
import dev.usbharu.hideout.application.config.HtmlSanitizeConfig
|
||||
import dev.usbharu.hideout.core.domain.exception.resource.DuplicateException
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
|
@ -36,7 +37,11 @@ class PostServiceImplTest {
|
|||
@Mock
|
||||
private lateinit var timelineService: TimelineService
|
||||
@Spy
|
||||
private var postBuilder: Post.PostBuilder = Post.PostBuilder(CharacterLimit())
|
||||
private var postBuilder: Post.PostBuilder = Post.PostBuilder(
|
||||
CharacterLimit(), DefaultPostContentFormatter(
|
||||
HtmlSanitizeConfig().policy()
|
||||
)
|
||||
)
|
||||
|
||||
@Mock
|
||||
private lateinit var apSendCreateService: ApSendCreateService
|
||||
|
|
|
@ -4,9 +4,11 @@ package dev.usbharu.hideout.core.service.user
|
|||
|
||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.application.config.CharacterLimit
|
||||
import dev.usbharu.hideout.application.config.HtmlSanitizeConfig
|
||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
import dev.usbharu.hideout.core.service.post.DefaultPostContentFormatter
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
@ -20,7 +22,7 @@ import kotlin.test.assertNull
|
|||
|
||||
class ActorServiceTest {
|
||||
val actorBuilder = Actor.UserBuilder(CharacterLimit(), ApplicationConfig(URL("https://example.com")))
|
||||
val postBuilder = Post.PostBuilder(CharacterLimit())
|
||||
val postBuilder = Post.PostBuilder(CharacterLimit(), DefaultPostContentFormatter(HtmlSanitizeConfig().policy()))
|
||||
@Test
|
||||
fun `createLocalUser ローカルユーザーを作成できる`() = runTest {
|
||||
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
package utils
|
||||
|
||||
import dev.usbharu.hideout.application.config.CharacterLimit
|
||||
import dev.usbharu.hideout.application.config.HtmlSanitizeConfig
|
||||
import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||
import dev.usbharu.hideout.core.service.post.DefaultPostContentFormatter
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.time.Instant
|
||||
|
||||
object PostBuilder {
|
||||
|
||||
private val postBuilder = Post.PostBuilder(CharacterLimit())
|
||||
private val postBuilder =
|
||||
Post.PostBuilder(CharacterLimit(), DefaultPostContentFormatter(HtmlSanitizeConfig().policy()))
|
||||
|
||||
private val idGenerator = TwitterSnowflakeIdGenerateService
|
||||
|
||||
|
@ -26,7 +29,7 @@ object PostBuilder {
|
|||
id = id,
|
||||
actorId = userId,
|
||||
overview = overview,
|
||||
text = text,
|
||||
content = text,
|
||||
createdAt = createdAt,
|
||||
visibility = visibility,
|
||||
url = url,
|
||||
|
|
Loading…
Reference in New Issue