mirror of https://github.com/usbharu/Hideout.git
feat: 投稿APIを追加
This commit is contained in:
parent
4ae7d09610
commit
411435200b
|
@ -1,15 +1,30 @@
|
||||||
package dev.usbharu.hideout.controller.mastodon
|
package dev.usbharu.hideout.controller.mastodon
|
||||||
|
|
||||||
import dev.usbharu.hideout.controller.mastodon.generated.DefaultApi
|
import dev.usbharu.hideout.controller.mastodon.generated.DefaultApi
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequest
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.V1Instance
|
import dev.usbharu.hideout.domain.mastodon.model.generated.V1Instance
|
||||||
|
import dev.usbharu.hideout.domain.model.UserDetailsImpl
|
||||||
import dev.usbharu.hideout.service.api.mastodon.InstanceApiService
|
import dev.usbharu.hideout.service.api.mastodon.InstanceApiService
|
||||||
|
import dev.usbharu.hideout.service.api.mastodon.StatusesApiService
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
|
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
class MastodonApiController(private val instanceApiService: InstanceApiService) : DefaultApi {
|
class MastodonApiController(
|
||||||
|
private val instanceApiService: InstanceApiService,
|
||||||
|
private val statusesApiService: StatusesApiService
|
||||||
|
) : DefaultApi {
|
||||||
override suspend fun apiV1InstanceGet(): ResponseEntity<V1Instance> {
|
override suspend fun apiV1InstanceGet(): ResponseEntity<V1Instance> {
|
||||||
return ResponseEntity(instanceApiService.v1Instance(), HttpStatus.OK)
|
return ResponseEntity(instanceApiService.v1Instance(), HttpStatus.OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV1StatusesPost(statusesRequest: StatusesRequest): ResponseEntity<Status> {
|
||||||
|
val principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()
|
||||||
|
require(principal is UserDetailsImpl)
|
||||||
|
return ResponseEntity(statusesApiService.postStatus(statusesRequest, principal), HttpStatus.OK)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package dev.usbharu.hideout.domain.model
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority
|
||||||
|
import org.springframework.security.core.userdetails.User
|
||||||
|
import java.io.Serial
|
||||||
|
|
||||||
|
class UserDetailsImpl(
|
||||||
|
val id: Long,
|
||||||
|
username: String?,
|
||||||
|
password: String?,
|
||||||
|
enabled: Boolean,
|
||||||
|
accountNonExpired: Boolean,
|
||||||
|
credentialsNonExpired: Boolean,
|
||||||
|
accountNonLocked: Boolean,
|
||||||
|
authorities: MutableCollection<out GrantedAuthority>?
|
||||||
|
) : User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities) {
|
||||||
|
companion object {
|
||||||
|
@Serial
|
||||||
|
private const val serialVersionUID: Long = -899168205656607781L
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package dev.usbharu.hideout.service.api.mastodon
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.StatusesRequest
|
||||||
|
import dev.usbharu.hideout.domain.model.UserDetailsImpl
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
|
import dev.usbharu.hideout.exception.FailedToGetResourcesException
|
||||||
|
import dev.usbharu.hideout.query.PostQueryService
|
||||||
|
import dev.usbharu.hideout.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.service.mastodon.AccountService
|
||||||
|
import dev.usbharu.hideout.service.post.PostService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Service
|
||||||
|
interface StatusesApiService {
|
||||||
|
suspend fun postStatus(statusesRequest: StatusesRequest, user: UserDetailsImpl): Status
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class StatsesApiServiceImpl(
|
||||||
|
private val postService: PostService,
|
||||||
|
private val accountService: AccountService,
|
||||||
|
private val postQueryService: PostQueryService,
|
||||||
|
private val userQueryService: UserQueryService
|
||||||
|
) :
|
||||||
|
StatusesApiService {
|
||||||
|
override suspend fun postStatus(statusesRequest: StatusesRequest, user: UserDetailsImpl): Status {
|
||||||
|
|
||||||
|
val visibility = when (statusesRequest.visibility) {
|
||||||
|
StatusesRequest.Visibility.public -> Visibility.PUBLIC
|
||||||
|
StatusesRequest.Visibility.unlisted -> Visibility.UNLISTED
|
||||||
|
StatusesRequest.Visibility.private -> Visibility.FOLLOWERS
|
||||||
|
StatusesRequest.Visibility.direct -> Visibility.DIRECT
|
||||||
|
null -> Visibility.PUBLIC
|
||||||
|
}
|
||||||
|
|
||||||
|
val post = postService.createLocal(
|
||||||
|
PostCreateDto(
|
||||||
|
statusesRequest.status.orEmpty(),
|
||||||
|
statusesRequest.spoilerText,
|
||||||
|
visibility,
|
||||||
|
null,
|
||||||
|
statusesRequest.inReplyToId?.toLongOrNull(),
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val account = accountService.findById(user.id)
|
||||||
|
|
||||||
|
val postVisibility = when (statusesRequest.visibility) {
|
||||||
|
StatusesRequest.Visibility.public -> Status.Visibility.public
|
||||||
|
StatusesRequest.Visibility.unlisted -> Status.Visibility.unlisted
|
||||||
|
StatusesRequest.Visibility.private -> Status.Visibility.private
|
||||||
|
StatusesRequest.Visibility.direct -> Status.Visibility.direct
|
||||||
|
null -> Status.Visibility.public
|
||||||
|
}
|
||||||
|
|
||||||
|
val replyUser = if (post.replyId != null) {
|
||||||
|
try {
|
||||||
|
userQueryService.findById(postQueryService.findById(post.replyId).userId).id
|
||||||
|
} catch (e: FailedToGetResourcesException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Status(
|
||||||
|
id = post.id.toString(),
|
||||||
|
uri = post.apId,
|
||||||
|
createdAt = Instant.ofEpochMilli(post.createdAt).toString(),
|
||||||
|
account = account,
|
||||||
|
content = post.text,
|
||||||
|
visibility = postVisibility,
|
||||||
|
sensitive = post.sensitive,
|
||||||
|
spoilerText = post.overview.orEmpty(),
|
||||||
|
mediaAttachments = emptyList(),
|
||||||
|
mentions = emptyList(),
|
||||||
|
tags = emptyList(),
|
||||||
|
emojis = emptyList(),
|
||||||
|
reblogsCount = 0,
|
||||||
|
favouritesCount = 0,
|
||||||
|
repliesCount = 0,
|
||||||
|
url = post.url,
|
||||||
|
post.replyId?.toString(),
|
||||||
|
inReplyToAccountId = replyUser?.toString(),
|
||||||
|
reblog = null,
|
||||||
|
language = null,
|
||||||
|
text = post.text,
|
||||||
|
editedAt = null,
|
||||||
|
application = null,
|
||||||
|
poll = null,
|
||||||
|
card = null,
|
||||||
|
favourited = null,
|
||||||
|
reblogged = null,
|
||||||
|
muted = null,
|
||||||
|
bookmarked = null,
|
||||||
|
pinned = null,
|
||||||
|
filtered = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,39 @@
|
||||||
package dev.usbharu.hideout.service.mastodon
|
package dev.usbharu.hideout.service.mastodon
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.mastodon.model.generated.Account
|
||||||
|
import dev.usbharu.hideout.query.UserQueryService
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
interface AccountService {
|
interface AccountService {
|
||||||
suspend fun findById()
|
suspend fun findById(id: Long): Account
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AccountServiceImpl(private val userQueryService: UserQueryService) : AccountService {
|
||||||
|
override suspend fun findById(id: Long): Account {
|
||||||
|
val findById = userQueryService.findById(id)
|
||||||
|
return Account(
|
||||||
|
id = findById.id.toString(),
|
||||||
|
username = findById.name,
|
||||||
|
acct = "${findById.name}@${findById.domain}",
|
||||||
|
url = findById.url,
|
||||||
|
displayName = findById.screenName,
|
||||||
|
note = findById.description,
|
||||||
|
avatar = findById.url + "/icon.jpg",
|
||||||
|
avatarStatic = findById.url + "/icon.jpg",
|
||||||
|
header = findById.url + "/header.jpg",
|
||||||
|
headerStatic = findById.url + "/header.jpg",
|
||||||
|
locked = false,
|
||||||
|
emptyList(),
|
||||||
|
emptyList(),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
findById.createdAt.toString(),
|
||||||
|
findById.createdAt.toString(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,27 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/V1Instance"
|
$ref: "#/components/schemas/V1Instance"
|
||||||
|
|
||||||
|
/api/v1/statuses:
|
||||||
|
post:
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:statuses"
|
||||||
|
requestBody:
|
||||||
|
description: 投稿する内容
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/StatusesRequest"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Status"
|
||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
Account:
|
Account:
|
||||||
|
@ -250,7 +271,7 @@ components:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/StatusMention"
|
$ref: "#/components/schemas/StatusMention"
|
||||||
tag:
|
tags:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/StatusTag"
|
$ref: "#/components/schemas/StatusTag"
|
||||||
|
@ -277,7 +298,7 @@ components:
|
||||||
$ref: "#/components/schemas/Status"
|
$ref: "#/components/schemas/Status"
|
||||||
poll:
|
poll:
|
||||||
$ref: "#/components/schemas/Poll"
|
$ref: "#/components/schemas/Poll"
|
||||||
PreviewCard:
|
card:
|
||||||
$ref: "#/components/schemas/PreviewCard"
|
$ref: "#/components/schemas/PreviewCard"
|
||||||
language:
|
language:
|
||||||
type: string
|
type: string
|
||||||
|
@ -302,7 +323,28 @@ components:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/FilterResult"
|
$ref: "#/components/schemas/FilterResult"
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- uri
|
||||||
|
- created_at
|
||||||
|
- account
|
||||||
|
- content
|
||||||
|
- visibility
|
||||||
|
- sensitive
|
||||||
|
- spoiler_text
|
||||||
|
- media_attachments
|
||||||
|
- mentions
|
||||||
|
- tags
|
||||||
|
- emojis
|
||||||
|
- reblogs_count
|
||||||
|
- favourites_count
|
||||||
|
- replies_count
|
||||||
|
- url
|
||||||
|
- in_reply_to_id
|
||||||
|
- in_reply_to_account_id
|
||||||
|
- language
|
||||||
|
- text
|
||||||
|
- edited_at
|
||||||
|
|
||||||
MediaAttachment:
|
MediaAttachment:
|
||||||
type: object
|
type: object
|
||||||
|
@ -844,3 +886,95 @@ components:
|
||||||
type: string
|
type: string
|
||||||
content:
|
content:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
StatusesRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
media_ids:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
poll:
|
||||||
|
$ref: "#/components/schemas/StatusesRequestPoll"
|
||||||
|
in_reply_to_id:
|
||||||
|
type: string
|
||||||
|
sensitive:
|
||||||
|
type: boolean
|
||||||
|
spoiler_text:
|
||||||
|
type: string
|
||||||
|
visibility:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- public
|
||||||
|
- unlisted
|
||||||
|
- private
|
||||||
|
- direct
|
||||||
|
language:
|
||||||
|
type: string
|
||||||
|
scheduled_at:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
StatusesRequestPoll:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
options:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
expires_in:
|
||||||
|
type: integer
|
||||||
|
multiple:
|
||||||
|
type: boolean
|
||||||
|
hide_totals:
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
securitySchemes:
|
||||||
|
OAuth2:
|
||||||
|
type: oauth2
|
||||||
|
description: Mastodon Oauth
|
||||||
|
flows:
|
||||||
|
authorizationCode:
|
||||||
|
authorizationUrl: /oauth/authorize
|
||||||
|
tokenUrl: /oauth/token
|
||||||
|
scopes:
|
||||||
|
read:accounts: ""
|
||||||
|
read:blocks: ""
|
||||||
|
read:bookmarks: ""
|
||||||
|
read:favourites: ""
|
||||||
|
read:filters: ""
|
||||||
|
read:follows: ""
|
||||||
|
read:lists: ""
|
||||||
|
read:mutes: ""
|
||||||
|
read:notifications: ""
|
||||||
|
read:search: ""
|
||||||
|
read:statuses: ""
|
||||||
|
write:accounts: ""
|
||||||
|
write:blocks: ""
|
||||||
|
write:bookmarks: ""
|
||||||
|
write:conversations: ""
|
||||||
|
write:favourites: ""
|
||||||
|
write:filters: ""
|
||||||
|
write:follows: ""
|
||||||
|
write:lists: ""
|
||||||
|
write:media: ""
|
||||||
|
write:mutes: ""
|
||||||
|
write:notifications: ""
|
||||||
|
write:reports: ""
|
||||||
|
write:statuses: ""
|
||||||
|
admin:read:accounts: ""
|
||||||
|
admin:read:reports: ""
|
||||||
|
admin:read:domain_allows: ""
|
||||||
|
admin:read:domain_blocks: ""
|
||||||
|
admin:read:ip_blocks: ""
|
||||||
|
admin:read:email_domain_blocks: ""
|
||||||
|
admin:read:canonical_email_blocks: ""
|
||||||
|
admin:write:accounts: ""
|
||||||
|
admin:write:reports: ""
|
||||||
|
admin:write:domain_allows: ""
|
||||||
|
admin:write:domain_blocks: ""
|
||||||
|
admin:write:ip_blocks: ""
|
||||||
|
admin:write:email_domain_blocks: ""
|
||||||
|
admin:write:canonical_email_blocks: ""
|
||||||
|
|
Loading…
Reference in New Issue