diff --git a/build.gradle.kts b/build.gradle.kts index a3b71261..91ca0a6a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -80,12 +80,14 @@ dependencies { implementation("org.xerial:sqlite-jdbc:3.40.1.0") implementation("io.ktor:ktor-server-websockets-jvm:$ktor_version") implementation("io.ktor:ktor-server-cio-jvm:$ktor_version") + implementation("io.ktor:ktor-server-compression:$ktor_version") implementation("ch.qos.logback:logback-classic:$logback_version") implementation("io.insert-koin:koin-core:$koin_version") implementation("io.insert-koin:koin-ktor:$koin_version") implementation("io.insert-koin:koin-logger-slf4j:$koin_version") implementation("io.insert-koin:koin-annotations:1.2.0") + implementation("io.ktor:ktor-server-compression-jvm:2.3.0") ksp("io.insert-koin:koin-ksp-compiler:1.2.0") diff --git a/openapitools.json b/openapitools.json new file mode 100644 index 00000000..a43405c3 --- /dev/null +++ b/openapitools.json @@ -0,0 +1,22 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.6.0", + "generators": { + "v3.0": { + "generatorName": "typescript-fetch", + "output": "src/main/web/generated", + "glob": "src/main/resources/openapi/api.yaml", + "additionalProperties": { + "modelPropertyNaming": "camelCase", + "supportsES6": true, + "withInterfaces": true, + "typescriptThreePlus": true, + "useSingleRequestParameter": false, + "prependFormOrBodyParameters": true + } + } + } + } +} diff --git a/package.json b/package.json index 50be56a0..a97a905b 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,26 @@ "name": "hideout", "version": "1.0.0", "dependencies": { - "solid-js": "^1.7.3" + "@solid-primitives/context": "^0.2.1", + "@solid-primitives/storage": "^1.3.11", + "@solidjs/router": "^0.8.2", + "@suid/icons-material": "^0.6.3", + "@suid/material": "^0.12.3", + "solid-js": "^1.7.6" }, "devDependencies": { + "@openapitools/openapi-generator-cli": "^2.6.0", + "@suid/vite-plugin": "^0.1.3", + "rollup-plugin-visualizer": "^5.9.2", "typescript": "^5.0.4", "vite": "4.2.3", - "vite-plugin-solid": "^2.7.0", - "@suid/vite-plugin": "^0.1.3" + "vite-plugin-solid": "^2.7.0" }, "scripts": { "start": "vite", "dev": "vite", "build": "vite build", - "serve": "vite preview" + "serve": "vite preview", + "gen-api": "openapi-generator-cli generate" } } diff --git a/src/main/kotlin/dev/usbharu/hideout/Application.kt b/src/main/kotlin/dev/usbharu/hideout/Application.kt index 1a993f96..4df106a6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/Application.kt +++ b/src/main/kotlin/dev/usbharu/hideout/Application.kt @@ -98,6 +98,7 @@ fun Application.parent() { runBlocking { inject().value.init() } + configureCompression() configureHTTP() configureStaticRouting() configureMonitoring() @@ -117,7 +118,11 @@ fun Application.parent() { activityPubUserService = inject().value, postService = inject().value, userApiService = inject().value, - reactionService = inject().value + reactionService = inject().value, + userAuthService = inject().value, + userRepository = inject().value, + jwtService = inject().value, + metaService = inject().value ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Note.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Note.kt index 8425fc79..21c2f25c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Note.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ap/Note.kt @@ -10,6 +10,8 @@ open class Note : Object { var inReplyTo: String? = null protected constructor() : super() + + @Suppress("LongParameterList") constructor( type: List = emptyList(), name: String, diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/PostResponse.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/PostResponse.kt new file mode 100644 index 00000000..c6845da9 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/PostResponse.kt @@ -0,0 +1,31 @@ +package dev.usbharu.hideout.domain.model.hideout.dto + +import dev.usbharu.hideout.domain.model.hideout.entity.Post +import dev.usbharu.hideout.domain.model.hideout.entity.User +import dev.usbharu.hideout.domain.model.hideout.entity.Visibility + +data class PostResponse( + val id: Long, + val user: UserResponse, + val overview: String? = null, + val text: String? = null, + val createdAt: Long, + val visibility: Visibility, + val url: String, + val sensitive: Boolean = false, +) { + companion object { + fun from(post: Post, user: User): PostResponse { + return PostResponse( + id = post.id, + user = UserResponse.from(user), + overview = post.overview, + text = post.text, + createdAt = post.createdAt, + visibility = post.visibility, + url = post.url, + sensitive = post.sensitive + ) + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Compression.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Compression.kt new file mode 100644 index 00000000..e13fa4c9 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Compression.kt @@ -0,0 +1,19 @@ +package dev.usbharu.hideout.plugins + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.plugins.compression.* + +fun Application.configureCompression() { + install(Compression) { + gzip { + matchContentType(ContentType.Application.JavaScript) + priority = 1.0 + } + deflate { + matchContentType(ContentType.Application.JavaScript) + priority = 10.0 + minimumSize(1024) // condition + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt index ef7957f9..8d4942f2 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Routing.kt @@ -1,8 +1,10 @@ package dev.usbharu.hideout.plugins +import dev.usbharu.hideout.repository.IUserRepository import dev.usbharu.hideout.routing.activitypub.inbox import dev.usbharu.hideout.routing.activitypub.outbox import dev.usbharu.hideout.routing.activitypub.usersAP +import dev.usbharu.hideout.routing.api.internal.v1.auth import dev.usbharu.hideout.routing.api.internal.v1.posts import dev.usbharu.hideout.routing.api.internal.v1.users import dev.usbharu.hideout.routing.wellknown.webfinger @@ -12,6 +14,9 @@ import dev.usbharu.hideout.service.api.IPostApiService import dev.usbharu.hideout.service.api.IUserApiService import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService import dev.usbharu.hideout.service.reaction.IReactionService +import dev.usbharu.hideout.service.auth.IJwtService +import dev.usbharu.hideout.service.core.IMetaService +import dev.usbharu.hideout.service.user.IUserAuthService import dev.usbharu.hideout.service.user.IUserService import io.ktor.server.application.* import io.ktor.server.plugins.autohead.* @@ -25,7 +30,11 @@ fun Application.configureRouting( activityPubUserService: ActivityPubUserService, postService: IPostApiService, userApiService: IUserApiService, - reactionService: IReactionService + reactionService: IReactionService, + userAuthService: IUserAuthService, + userRepository: IUserRepository, + jwtService: IJwtService, + metaService: IMetaService ) { install(AutoHeadResponse) routing { @@ -36,6 +45,7 @@ fun Application.configureRouting( route("/api/internal/v1") { posts(postService, reactionService) users(userService, userApiService) + auth(userAuthService, userRepository, jwtService) } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt b/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt index a98eee18..84966882 100644 --- a/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt +++ b/src/main/kotlin/dev/usbharu/hideout/plugins/Security.kt @@ -2,19 +2,12 @@ package dev.usbharu.hideout.plugins import com.auth0.jwk.JwkProvider import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken -import dev.usbharu.hideout.domain.model.hideout.form.UserLogin -import dev.usbharu.hideout.exception.UserNotFoundException -import dev.usbharu.hideout.repository.IUserRepository -import dev.usbharu.hideout.service.auth.IJwtService import dev.usbharu.hideout.service.core.IMetaService -import dev.usbharu.hideout.service.user.IUserAuthService import dev.usbharu.hideout.util.JsonWebKeyUtil import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.auth.jwt.* -import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* @@ -22,11 +15,8 @@ const val TOKEN_AUTH = "jwt-auth" @Suppress("MagicNumber") fun Application.configureSecurity( - userAuthService: IUserAuthService, - metaService: IMetaService, - userRepository: IUserRepository, - jwtService: IJwtService, - jwkProvider: JwkProvider + jwkProvider: JwkProvider, + metaService: IMetaService ) { val issuer = Config.configData.url install(Authentication) { @@ -48,24 +38,6 @@ fun Application.configureSecurity( } routing { - post("/login") { - val loginUser = call.receive() - val check = userAuthService.verifyAccount(loginUser.username, loginUser.password) - if (check.not()) { - return@post call.respond(HttpStatusCode.Unauthorized) - } - - val user = userRepository.findByNameAndDomain(loginUser.username, Config.configData.domain) - ?: throw UserNotFoundException("${loginUser.username} was not found.") - - return@post call.respond(jwtService.createToken(user)) - } - - post("/refresh-token") { - val refreshToken = call.receive() - return@post call.respond(jwtService.refreshToken(refreshToken)) - } - get("/.well-known/jwks.json") { //language=JSON val jwt = metaService.getJwtMeta() @@ -74,12 +46,5 @@ fun Application.configureSecurity( text = JsonWebKeyUtil.publicKeyToJwk(jwt.publicKey, jwt.kid.toString()) ) } - authenticate(TOKEN_AUTH) { - get("/auth-check") { - val principal = call.principal() ?: throw IllegalStateException("no principal") - val username = principal.payload.getClaim("uid") - call.respondText("Hello $username") - } - } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt index 6360e609..12156818 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/PostRepositoryImpl.kt @@ -88,7 +88,9 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe limit: Int?, userId: Long? ): List { - TODO("Not yet implemented") + return query { + Posts.select { Posts.visibility eq Visibility.PUBLIC.ordinal }.map { it.toPost() } + } } override suspend fun findByUserNameAndDomain( diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Auth.kt b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Auth.kt new file mode 100644 index 00000000..f185e832 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Auth.kt @@ -0,0 +1,48 @@ +package dev.usbharu.hideout.routing.api.internal.v1 + +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken +import dev.usbharu.hideout.domain.model.hideout.form.UserLogin +import dev.usbharu.hideout.exception.UserNotFoundException +import dev.usbharu.hideout.plugins.TOKEN_AUTH +import dev.usbharu.hideout.repository.IUserRepository +import dev.usbharu.hideout.service.auth.IJwtService +import dev.usbharu.hideout.service.user.IUserAuthService +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.auth( + userAuthService: IUserAuthService, + userRepository: IUserRepository, + jwtService: IJwtService +) { + post("/login") { + val loginUser = call.receive() + val check = userAuthService.verifyAccount(loginUser.username, loginUser.password) + if (check.not()) { + return@post call.respond(HttpStatusCode.Unauthorized) + } + + val user = userRepository.findByNameAndDomain(loginUser.username, Config.configData.domain) + ?: throw UserNotFoundException("${loginUser.username} was not found.") + + return@post call.respond(jwtService.createToken(user)) + } + + post("/refresh-token") { + val refreshToken = call.receive() + return@post call.respond(jwtService.refreshToken(refreshToken)) + } + authenticate(TOKEN_AUTH) { + get("/auth-check") { + val principal = call.principal() ?: throw IllegalStateException("no principal") + val username = principal.payload.getClaim("uid") + call.respondText("Hello $username") + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/IPostApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/IPostApiService.kt index eb7eb97a..6aa54403 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/IPostApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/IPostApiService.kt @@ -1,12 +1,12 @@ package dev.usbharu.hideout.service.api -import dev.usbharu.hideout.domain.model.hideout.entity.Post +import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse import java.time.Instant @Suppress("LongParameterList") interface IPostApiService { - suspend fun createPost(postForm: dev.usbharu.hideout.domain.model.hideout.form.Post, userId: Long): Post - suspend fun getById(id: Long, userId: Long?): Post + suspend fun createPost(postForm: dev.usbharu.hideout.domain.model.hideout.form.Post, userId: Long): PostResponse + suspend fun getById(id: Long, userId: Long?): PostResponse suspend fun getAll( since: Instant? = null, until: Instant? = null, @@ -14,7 +14,7 @@ interface IPostApiService { maxId: Long? = null, limit: Int? = null, userId: Long? = null - ): List + ): List suspend fun getByUser( nameOrId: String, @@ -24,5 +24,5 @@ interface IPostApiService { maxId: Long? = null, limit: Int? = null, userId: Long? = null - ): List + ): List } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/PostApiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/PostApiServiceImpl.kt index 35e05585..b8957f87 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/PostApiServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/PostApiServiceImpl.kt @@ -2,11 +2,16 @@ package dev.usbharu.hideout.service.api import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto -import dev.usbharu.hideout.domain.model.hideout.entity.Post -import dev.usbharu.hideout.exception.PostNotFoundException -import dev.usbharu.hideout.repository.IPostRepository +import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse +import dev.usbharu.hideout.repository.* import dev.usbharu.hideout.service.post.IPostService import dev.usbharu.hideout.util.AcctUtil +import kotlinx.coroutines.Dispatchers +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.innerJoin +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.koin.core.annotation.Single import java.time.Instant import dev.usbharu.hideout.domain.model.hideout.form.Post as FormPost @@ -14,10 +19,11 @@ import dev.usbharu.hideout.domain.model.hideout.form.Post as FormPost @Single class PostApiServiceImpl( private val postService: IPostService, - private val postRepository: IPostRepository + private val postRepository: IPostRepository, + private val userRepository: IUserRepository ) : IPostApiService { - override suspend fun createPost(postForm: FormPost, userId: Long): Post { - return postService.createLocal( + override suspend fun createPost(postForm: FormPost, userId: Long): PostResponse { + val createdPost = postService.createLocal( PostCreateDto( text = postForm.text, overview = postForm.overview, @@ -27,11 +33,20 @@ class PostApiServiceImpl( userId = userId ) ) + val creator = userRepository.findById(userId) + return PostResponse.from(createdPost, creator!!) } - override suspend fun getById(id: Long, userId: Long?): Post { - return postRepository.findOneById(id, userId) - ?: throw PostNotFoundException("$id was not found or is not authorized.") + @Suppress("InjectDispatcher") + suspend fun query(block: suspend () -> T): T = + newSuspendedTransaction(Dispatchers.IO) { block() } + + override suspend fun getById(id: Long, userId: Long?): PostResponse { + val query = query { + Posts.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { Users.id }).select { Posts.id eq id } + .single() + } + return PostResponse.from(query.toPost(), query.toUser()) } override suspend fun getAll( @@ -41,7 +56,12 @@ class PostApiServiceImpl( maxId: Long?, limit: Int?, userId: Long? - ): List = postRepository.findAll(since, until, minId, maxId, limit, userId) + ): List { + return query { + Posts.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id }).selectAll() + .map { PostResponse.from(it.toPost(), it.toUser()) } + } + } override suspend fun getByUser( nameOrId: String, @@ -51,23 +71,22 @@ class PostApiServiceImpl( maxId: Long?, limit: Int?, userId: Long? - ): List { + ): List { val idOrNull = nameOrId.toLongOrNull() return if (idOrNull == null) { val acct = AcctUtil.parse(nameOrId) - postRepository.findByUserNameAndDomain( - username = acct.username, - s = acct.domain - ?: Config.configData.domain, - since = since, - until = until, - minId = minId, - maxId = maxId, - limit = limit, - userId = userId - ) + query { + Posts.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id }).select { + Users.name.eq(acct.username) + .and(Users.domain eq (acct.domain ?: Config.configData.domain)) + }.map { PostResponse.from(it.toPost(), it.toUser()) } + } } else { - postRepository.findByUserId(idOrNull, since, until, minId, maxId, limit, userId) + query { + Posts.innerJoin(Users, onColumn = { Posts.userId }, otherColumn = { id }).select { + Posts.userId eq idOrNull + }.map { PostResponse.from(it.toPost(), it.toUser()) } + } } } } diff --git a/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt index f2cccc4d..c9ff10c5 100644 --- a/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt +++ b/src/main/kotlin/dev/usbharu/kjob/exposed/ExposedJobRepository.kt @@ -73,6 +73,7 @@ class ExposedJobRepository( } } + @Suppress("SuspendFunWithFlowReturnType") override suspend fun findNext(names: Set, status: Set, limit: Int): Flow { return query { jobs.select( diff --git a/src/main/resources/openapi/api.yaml b/src/main/resources/openapi/api.yaml index 5ff2768a..3cc79aab 100644 --- a/src/main/resources/openapi/api.yaml +++ b/src/main/resources/openapi/api.yaml @@ -20,7 +20,7 @@ paths: schema: type: array items: - $ref: "#/components/schemas/Post" + $ref: "#/components/schemas/PostResponse" 401: $ref: "#/components/responses/Unauthorized" 403: @@ -37,7 +37,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Post" + $ref: "#/components/schemas/PostRequest" responses: 200: description: 成功 @@ -65,7 +65,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Post" + $ref: "#/components/schemas/PostResponse" 401: $ref: "#/components/responses/Unauthorized" 403: @@ -90,7 +90,7 @@ paths: schema: type: array items: - $ref: "#/components/schemas/Post" + $ref: "#/components/schemas/PostResponse" 401: $ref: "#/components/responses/Unauthorized" 403: @@ -114,7 +114,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Post" + $ref: "#/components/schemas/PostResponse" 401: $ref: "#/components/responses/Unauthorized" 403: @@ -137,7 +137,7 @@ paths: schema: type: array items: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/UserResponse" post: summary: ユーザーを作成する @@ -181,7 +181,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/UserResponse" 404: $ref: "#/components/responses/NotFound" @@ -198,7 +198,7 @@ paths: schema: type: array items: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/UserResponse" post: summary: ユーザーをフォローする security: @@ -228,7 +228,47 @@ paths: schema: type: array items: - $ref: "#/components/schemas/User" + $ref: "#/components/schemas/UserResponse" + + /login: + post: + summary: ログインする + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UserLogin" + responses: + 200: + description: ログイン成功 + content: + application/json: + schema: + $ref: "#/components/schemas/JwtToken" + + /refresh-token: + post: + summary: 期限切れトークンの再発行をする + responses: + 200: + description: トークンの再発行に成功 + content: + application/json: + schema: + $ref: "#/components/schemas/JwtToken" + + /auth-check: + get: + summary: 認証チェック + responses: + 200: + description: 認証に成功 + content: + text/plain: + schema: + type: string + components: responses: @@ -261,8 +301,23 @@ components: type: string schemas: - User: + Visibility: + type: string + enum: + - public + - unlisted + - followers + - direct + UserResponse: type: object + required: + - id + - name + - domain + - screenName + - description + - url + - createdAt properties: id: type: number @@ -277,23 +332,30 @@ components: type: string description: type: string + nullable: true url: type: string readOnly: true createdAt: type: number readOnly: true - Post: + PostResponse: type: object + required: + - id + - user + - text + - createdAt + - visibility + - url + - sensitive properties: id: type: integer format: int64 readOnly: true - userId: - type: integer - format: int64 - readOnly: true + user: + $ref: "#/components/schemas/UserResponse" overview: type: string text: @@ -303,12 +365,7 @@ components: format: int64 readOnly: true visibility: - type: string - enum: - - public - - unlisted - - followers - - direct + $ref: "#/components/schemas/Visibility" url: type: string format: uri @@ -323,13 +380,49 @@ components: readOnly: true sensitive: type: boolean - apId: - type: string - format: url - readOnly: true + PostRequest: + type: object + properties: + overview: + type: string + text: + type: string + visibility: + $ref: "#/components/schemas/Visibility" + repostId: + type: integer + format: int64 + replyId: + type: integer + format: int64 + sensitive: + type: boolean + + JwtToken: + type: object + properties: + token: + type: string + refreshToken: + type: string + + RefreshToken: + type: object + properties: + refreshToken: + type: string + + UserLogin: + type: object + properties: + username: + type: string + password: + type: string securitySchemes: BearerAuth: type: http scheme: bearer + bearerFormat: JWT diff --git a/src/main/web/App.tsx b/src/main/web/App.tsx index 0da03fa6..3a833754 100644 --- a/src/main/web/App.tsx +++ b/src/main/web/App.tsx @@ -1,58 +1,44 @@ -import {Component, createSignal} from "solid-js"; +import {Component, createEffect, createSignal} from "solid-js"; +import {Route, Router, Routes} from "@solidjs/router"; +import {TopPage} from "./pages/TopPage"; +import {createTheme, CssBaseline, ThemeProvider, useMediaQuery} from "@suid/material"; +import {createCookieStorage} from "@solid-primitives/storage"; +import {ApiProvider} from "./lib/ApiProvider"; +import {Configuration, DefaultApi} from "./generated"; +import {LoginPage} from "./pages/LoginPage"; export const App: Component = () => { + const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); + const [cookie, setCookie] = createCookieStorage() + const [api, setApi] = createSignal(new DefaultApi(new Configuration({ + basePath: window.location.origin + "/api/internal/v1", + accessToken: cookie.token as string + }))) - const fn = (form: HTMLButtonElement) => { - console.log(form) - } + createEffect(() => { + setApi( + new DefaultApi(new Configuration({ + basePath: window.location.origin + "/api/internal/v1", + accessToken : cookie.token as string + }))) + }) - const [username, setUsername] = createSignal("") - const [password, setPassword] = createSignal("") - - return ( -
res.json()) - // .then(res => fetch("/auth-check", { - // method: "GET", - // headers: { - // 'Authorization': 'Bearer ' + res.token - // } - // })) - // .then(res => res.json()) - .then(res => { - console.log(res.token); - fetch("/refresh-token", { - method: "POST", - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({refreshToken: res.refreshToken}), - }).then(res=> res.json()).then(res => console.log(res.token)) - }) + const theme = createTheme({ + palette: { + mode: prefersDarkMode() ? 'dark' : 'light', } - - }> - setUsername(e.currentTarget.value)}/> - setPassword(e.currentTarget.value)}/> - -
+ }) + return ( + + + + + + + + + + + ) } - - -declare module 'solid-js' { - namespace JSX { - interface Directives { - fn: (form: HTMLFormElement) => void - } - } -} diff --git a/src/main/web/atoms/Avatar.tsx b/src/main/web/atoms/Avatar.tsx new file mode 100644 index 00000000..76db2222 --- /dev/null +++ b/src/main/web/atoms/Avatar.tsx @@ -0,0 +1,8 @@ +import {Avatar as SuidAvatar} from "@suid/material"; +import {Component, JSXElement} from "solid-js"; + +export const Avatar: Component<{ src: string }> = (props): JSXElement => { + return ( + + ) +} \ No newline at end of file diff --git a/src/main/web/atoms/SidebarButton.tsx b/src/main/web/atoms/SidebarButton.tsx new file mode 100644 index 00000000..cfce597a --- /dev/null +++ b/src/main/web/atoms/SidebarButton.tsx @@ -0,0 +1,14 @@ +import {ParentComponent} from "solid-js"; +import {Button, ListItem, ListItemAvatar, ListItemButton, ListItemIcon, ListItemText} from "@suid/material"; +import {Link} from "@solidjs/router"; + +export const SidebarButton: ParentComponent<{ text: string,linkTo:string }> = (props) => { + return ( + + + {props.children} + + + + ) +} diff --git a/src/main/web/lib/ApiProvider.tsx b/src/main/web/lib/ApiProvider.tsx new file mode 100644 index 00000000..006f9897 --- /dev/null +++ b/src/main/web/lib/ApiProvider.tsx @@ -0,0 +1,8 @@ +import {createContextProvider} from "@solid-primitives/context"; +import {createSignal} from "solid-js"; +import {DefaultApi, DefaultApiInterface} from "../generated"; + +export const [ApiProvider,useApi] = createContextProvider((props:{api:DefaultApiInterface}) => { + const [api,setApi] = createSignal(props.api); + return api +},()=>new DefaultApi()); diff --git a/src/main/web/lib/ApiWrapper.ts b/src/main/web/lib/ApiWrapper.ts new file mode 100644 index 00000000..2fbf3266 --- /dev/null +++ b/src/main/web/lib/ApiWrapper.ts @@ -0,0 +1,16 @@ +import {DefaultApiInterface} from "../generated"; + +export class ApiWrapper { + api: DefaultApiInterface; + + constructor(initApi: DefaultApiInterface) { + this.api = initApi; + console.log(this.api); + console.log(this.postsGet()); + } + + postsGet = async () => this.api.postsGet() + + usersUserNameGet = async (userName: string) => this.api.usersUserNameGet(userName); + +} diff --git a/src/main/web/molecules/ShareScopeIndicator.tsx b/src/main/web/molecules/ShareScopeIndicator.tsx new file mode 100644 index 00000000..7d5ccc7c --- /dev/null +++ b/src/main/web/molecules/ShareScopeIndicator.tsx @@ -0,0 +1,29 @@ +import {Component, Match, Switch} from "solid-js"; +import {Home, Lock, Mail, Public} from "@suid/icons-material"; +import {IconButton} from "@suid/material"; +import {Visibility} from "../generated"; + +export const ShareScopeIndicator: Component<{ visibility: Visibility }> = (props) => { + return }> + + + + + + + + + + + + + + + + + + + + + +} diff --git a/src/main/web/organisms/Post.tsx b/src/main/web/organisms/Post.tsx new file mode 100644 index 00000000..f3680354 --- /dev/null +++ b/src/main/web/organisms/Post.tsx @@ -0,0 +1,45 @@ +import {Component, createSignal} from "solid-js"; +import {Box, Card, CardActions, CardContent, CardHeader, IconButton, Menu, MenuItem, Typography} from "@suid/material"; +import {Avatar} from "../atoms/Avatar"; +import {Favorite, MoreVert, Reply, ScreenRotationAlt} from "@suid/icons-material"; +import {ShareScopeIndicator} from "../molecules/ShareScopeIndicator"; +import {PostResponse} from "../generated"; + +export const Post: Component<{ post: PostResponse }> = (props) => { + const [anchorEl, setAnchorEl] = createSignal(null) + const open = () => Boolean(anchorEl()); + const handleClose = () => { + setAnchorEl(null); + } + + return ( + + } title={props.post.user.screenName} + subheader={`${props.post.user.name}@${props.post.user.domain}`} + action={ { + setAnchorEl(event.currentTarget) + }}>aaa }/> + + + {props.post.text} + + + + + + + + + + + + + + {new Date(props.post.createdAt).toDateString()} + + + + + ) +} diff --git a/src/main/web/organisms/PostForm.tsx b/src/main/web/organisms/PostForm.tsx new file mode 100644 index 00000000..3d11515a --- /dev/null +++ b/src/main/web/organisms/PostForm.tsx @@ -0,0 +1,43 @@ +import {Component, createSignal} from "solid-js"; +import {Button, IconButton, Paper, Stack, TextField, Typography} from "@suid/material"; +import {Avatar} from "../atoms/Avatar"; +import {AddPhotoAlternate, Poll, Public} from "@suid/icons-material"; +import {useApi} from "../lib/ApiProvider"; + +export const PostForm: Component<{ label: string }> = (props) => { + const [text, setText] = createSignal("") + const api = useApi() + return ( + + + + + setText(event.target.value)} fullWidth/> + + + + + + + + + + + + + + + + aaa + + + + + + + ) +} diff --git a/src/main/web/pages/LoginPage.tsx b/src/main/web/pages/LoginPage.tsx new file mode 100644 index 00000000..decb6990 --- /dev/null +++ b/src/main/web/pages/LoginPage.tsx @@ -0,0 +1,58 @@ +import {Button, Card, CardContent, CardHeader, Modal, Stack, TextField} from "@suid/material"; +import {Component, createSignal} from "solid-js"; +import {createCookieStorage} from "@solid-primitives/storage"; +import {useApi} from "../lib/ApiProvider"; +import {useNavigate} from "@solidjs/router"; + +export const LoginPage: Component = () => { + const [username, setUsername] = createSignal("") + const [password, setPassword] = createSignal("") + + const [cookie, setCookie] = createCookieStorage(); + + const navigator = useNavigate(); + + const api = useApi(); + + const onSubmit: () => void = () => { + api().loginPost({password: password(), username: username()}).then(value => { + setCookie("token", value.token); + setCookie("refresh-token", value.refreshToken) + navigator("/") + }).catch(reason => { + console.log(reason); + setPassword("") + }) + } + + return ( + + + + + + + + setUsername(event.target.value)} + label="Username" + type="text" + autoComplete="username" + variant="standard" + /> + setPassword(event.target.value)} + label="Password" + type="password" + autoComplete="current-password" + variant="standard" + /> + + + + + + ) +} diff --git a/src/main/web/pages/TopPage.tsx b/src/main/web/pages/TopPage.tsx new file mode 100644 index 00000000..b62c2665 --- /dev/null +++ b/src/main/web/pages/TopPage.tsx @@ -0,0 +1,24 @@ +import {Component} from "solid-js"; +import {MainPage} from "../templates/MainPage"; +import {PostForm} from "../organisms/PostForm"; +import {Stack} from "@suid/material"; +import {PostResponse} from "../generated"; +import {PostList} from "../templates/PostList"; +import {useApi} from "../lib/ApiProvider"; +import {createStore} from "solid-js/store"; + + +export const TopPage: Component = () => { + const api = useApi() + const [posts, setPosts] = createStore([]) + api().postsGet().then((res)=>setPosts(res)) + + return ( + + + + + + + ) +} diff --git a/src/main/web/templates/MainPage.tsx b/src/main/web/templates/MainPage.tsx new file mode 100644 index 00000000..cf183a8e --- /dev/null +++ b/src/main/web/templates/MainPage.tsx @@ -0,0 +1,20 @@ +import {createSignal, ParentComponent} from "solid-js"; +import {Grid} from "@suid/material"; +import {Sidebar} from "./Sidebar"; + +export const MainPage: ParentComponent = (props) => { + return ( + + + + + + {props.children} + + + + + + + ) +} diff --git a/src/main/web/templates/PostList.tsx b/src/main/web/templates/PostList.tsx new file mode 100644 index 00000000..82930d8e --- /dev/null +++ b/src/main/web/templates/PostList.tsx @@ -0,0 +1,14 @@ +import {Component, For} from "solid-js"; +import {CircularProgress} from "@suid/material"; +import {Post} from "../organisms/Post"; +import {PostResponse} from "../generated"; + +export const PostList: Component<{ posts: PostResponse[] | undefined }> = (props) => { + return ( + }> + { + (item, index) => + } + + ) +} diff --git a/src/main/web/templates/Sidebar.tsx b/src/main/web/templates/Sidebar.tsx new file mode 100644 index 00000000..cf8d16de --- /dev/null +++ b/src/main/web/templates/Sidebar.tsx @@ -0,0 +1,13 @@ +import {Component} from "solid-js"; +import {Button, List, Stack} from "@suid/material"; +import {Home} from "@suid/icons-material"; +import {SidebarButton} from "../atoms/SidebarButton"; + +export const Sidebar: Component = (props) => { + return ( + + + + + ) +} diff --git a/src/test/kotlin/dev/usbharu/hideout/plugins/SecurityKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/plugins/SecurityKtTest.kt index 9da64ff0..8c1f4d13 100644 --- a/src/test/kotlin/dev/usbharu/hideout/plugins/SecurityKtTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/plugins/SecurityKtTest.kt @@ -15,6 +15,7 @@ import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken import dev.usbharu.hideout.domain.model.hideout.form.UserLogin import dev.usbharu.hideout.exception.InvalidRefreshTokenException import dev.usbharu.hideout.repository.IUserRepository +import dev.usbharu.hideout.routing.api.internal.v1.auth import dev.usbharu.hideout.service.auth.IJwtService import dev.usbharu.hideout.service.core.IMetaService import dev.usbharu.hideout.service.user.IUserAuthService @@ -24,6 +25,7 @@ import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.server.config.* +import io.ktor.server.routing.* import io.ktor.server.testing.* import org.junit.jupiter.api.Test import org.mockito.ArgumentMatchers.anyString @@ -70,7 +72,10 @@ class SecurityKtTest { val jwkProvider = mock() application { configureSerialization() - configureSecurity(userAuthService, metaService, userRepository, jwtService, jwkProvider) + configureSecurity(jwkProvider, metaService) + routing { + auth(userAuthService, userRepository, jwtService) + } } client.post("/login") { @@ -97,7 +102,10 @@ class SecurityKtTest { val jwkProvider = mock() application { configureSerialization() - configureSecurity(userAuthService, metaService, userRepository, jwtService, jwkProvider) + configureSecurity(jwkProvider, metaService) + routing { + auth(userAuthService, userRepository, jwtService) + } } client.post("/login") { contentType(ContentType.Application.Json) @@ -122,7 +130,10 @@ class SecurityKtTest { val jwkProvider = mock() application { configureSerialization() - configureSecurity(userAuthService, metaService, userRepository, jwtService, jwkProvider) + configureSecurity(jwkProvider, metaService) + routing { + auth(userAuthService, userRepository, jwtService) + } } client.post("/login") { contentType(ContentType.Application.Json) @@ -140,7 +151,10 @@ class SecurityKtTest { Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper()) application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) + routing { + auth(mock(), mock(), mock()) + } } client.get("/auth-check").apply { assertEquals(HttpStatusCode.Unauthorized, call.response.status) @@ -155,7 +169,10 @@ class SecurityKtTest { Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper()) application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) + routing { + auth(mock(), mock(), mock()) + } } client.get("/auth-check") { header("Authorization", "Digest dsfjjhogalkjdfmlhaog") @@ -172,7 +189,10 @@ class SecurityKtTest { Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper()) application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) + routing { + auth(mock(), mock(), mock()) + } } client.get("/auth-check") { header("Authorization", "") @@ -190,7 +210,10 @@ class SecurityKtTest { application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) + routing { + auth(mock(), mock(), mock()) + } } client.get("/auth-check") { header("Authorization", "Bearer ") @@ -244,11 +267,12 @@ class SecurityKtTest { ) ) } - val userRepository = mock() - val jwtService = mock() application { configureSerialization() - configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider) + configureSecurity(jwkProvider, metaService) + routing { + auth(mock(), mock(), mock()) + } } client.get("/auth-check") { @@ -304,11 +328,12 @@ class SecurityKtTest { ) ) } - val userRepository = mock() - val jwtService = mock() application { configureSerialization() - configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider) + configureSecurity(jwkProvider, metaService) + routing { + auth(mock(), mock(), mock()) + } } client.get("/auth-check") { header("Authorization", "Bearer $token") @@ -362,11 +387,12 @@ class SecurityKtTest { ) ) } - val userRepository = mock() - val jwtService = mock() application { configureSerialization() - configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider) + configureSecurity(jwkProvider, metaService) + routing { + auth(mock(), mock(), mock()) + } } client.get("/auth-check") { header("Authorization", "Bearer $token") @@ -420,11 +446,12 @@ class SecurityKtTest { ) ) } - val userRepository = mock() - val jwtService = mock() application { configureSerialization() - configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider) + configureSecurity(jwkProvider, metaService) + routing { + auth(mock(), mock(), mock()) + } } client.get("/auth-check") { header("Authorization", "Bearer $token") @@ -477,11 +504,12 @@ class SecurityKtTest { ) ) } - val userRepository = mock() - val jwtService = mock() application { configureSerialization() - configureSecurity(mock(), metaService, userRepository, jwtService, jwkProvider) + configureSecurity(jwkProvider, metaService) + routing { + auth(mock(), mock(), mock()) + } } client.get("/auth-check") { header("Authorization", "Bearer $token") @@ -501,7 +529,10 @@ class SecurityKtTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), jwtService, mock()) + configureSecurity(mock(), mock()) + routing { + auth(mock(), mock(), jwtService) + } } client.post("/refresh-token") { header("Content-Type", "application/json") @@ -523,7 +554,10 @@ class SecurityKtTest { application { configureStatusPages() configureSerialization() - configureSecurity(mock(), mock(), mock(), jwtService, mock()) + configureSecurity(mock(), mock()) + routing { + auth(mock(), mock(), jwtService) + } } client.post("/refresh-token") { header("Content-Type", "application/json") diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsTest.kt index 75d486fd..2bfc150e 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsTest.kt @@ -4,6 +4,8 @@ import com.auth0.jwt.interfaces.Claim import com.auth0.jwt.interfaces.Payload import com.fasterxml.jackson.module.kotlin.readValue import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.domain.model.hideout.dto.PostResponse +import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.domain.model.hideout.entity.Visibility import dev.usbharu.hideout.plugins.TOKEN_AUTH @@ -32,18 +34,27 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } + val user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ) val posts = listOf( - Post( + PostResponse( id = 12345, - userId = 4321, + user = user, text = "test1", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" ), - Post( + PostResponse( id = 123456, - userId = 4322, + user = user, text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -64,7 +75,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) @@ -89,27 +100,35 @@ class PostsTest { val payload = mock { on { getClaim(eq("uid")) } doReturn claim } - + val user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ) val posts = listOf( - Post( + PostResponse( id = 12345, - userId = 4321, + user = user, text = "test1", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" ), - Post( + PostResponse( id = 123456, - userId = 4322, + user = user, text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/2" ), - Post( + PostResponse( id = 1234567, - userId = 4333, + user = user, text = "Followers only", visibility = Visibility.FOLLOWERS, createdAt = Instant.now().toEpochMilli(), @@ -156,9 +175,18 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } - val post = Post( - 12345, - 1234, + val user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ) + val post = PostResponse( + id = 12345, + user = user, text = "aaa", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -169,7 +197,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) @@ -187,9 +215,17 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } - val post = Post( + val post = PostResponse( 12345, - 1234, + UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ), text = "aaa", visibility = Visibility.FOLLOWERS, createdAt = Instant.now().toEpochMilli(), @@ -242,14 +278,22 @@ class PostsTest { onBlocking { createPost(any(), any()) } doAnswer { val argument = it.getArgument(0) val userId = it.getArgument(1) - Post( - 123L, - userId, - null, - argument.text, - Instant.now().toEpochMilli(), - Visibility.PUBLIC, - "https://example.com" + PostResponse( + id = 123L, + user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ), + overview = null, + text = argument.text, + createdAt = Instant.now().toEpochMilli(), + visibility = Visibility.PUBLIC, + url = "https://example.com" ) } } @@ -290,18 +334,27 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } + val user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ) val posts = listOf( - Post( + PostResponse( id = 12345, - userId = 1, + user = user, text = "test1", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" ), - Post( + PostResponse( id = 123456, - userId = 1, + user = user, text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -323,7 +376,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) @@ -342,18 +395,27 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } + val user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ) val posts = listOf( - Post( + PostResponse( id = 12345, - userId = 1, + user = user, text = "test1", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" ), - Post( + PostResponse( id = 123456, - userId = 1, + user = user, text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -375,7 +437,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) @@ -394,18 +456,27 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } + val user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ) val posts = listOf( - Post( + PostResponse( id = 12345, - userId = 1, + user = user, text = "test1", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" ), - Post( + PostResponse( id = 123456, - userId = 1, + user = user, text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -427,7 +498,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) @@ -446,18 +517,27 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } + val user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ) val posts = listOf( - Post( + PostResponse( id = 12345, - userId = 1, + user = user, text = "test1", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" ), - Post( + PostResponse( id = 123456, - userId = 1, + user = user, text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -479,7 +559,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) @@ -498,9 +578,17 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } - val post = Post( + val post = PostResponse( id = 123456, - userId = 1, + user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ), text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -511,7 +599,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) @@ -530,9 +618,17 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } - val post = Post( + val post = PostResponse( id = 123456, - userId = 1, + user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ), text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -543,7 +639,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) @@ -562,9 +658,17 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } - val post = Post( + val post = PostResponse( id = 123456, - userId = 1, + user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ), text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -575,7 +679,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) @@ -594,9 +698,17 @@ class PostsTest { environment { config = ApplicationConfig("empty.conf") } - val post = Post( + val post = PostResponse( id = 123456, - userId = 1, + user = UserResponse( + id = 54321, + name = "user1", + domain = "example.com", + screenName = "user 1", + description = "Test user", + url = "https://example.com/users/54321", + createdAt = Instant.now().toEpochMilli() + ), text = "test2", visibility = Visibility.PUBLIC, createdAt = Instant.now().toEpochMilli(), @@ -607,7 +719,7 @@ class PostsTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { posts(postService, mock()) diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt index b21c80b7..c9fdf8fd 100644 --- a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt @@ -58,7 +58,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userService) @@ -96,7 +96,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(userService, mock()) @@ -127,7 +127,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(userService, mock()) @@ -162,7 +162,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) @@ -195,7 +195,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) @@ -228,7 +228,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) @@ -261,7 +261,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) @@ -306,7 +306,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) @@ -351,7 +351,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) @@ -396,7 +396,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) @@ -591,7 +591,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) @@ -636,7 +636,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) @@ -681,7 +681,7 @@ class UsersTest { } application { configureSerialization() - configureSecurity(mock(), mock(), mock(), mock(), mock()) + configureSecurity(mock(), mock()) routing { route("/api/internal/v1") { users(mock(), userApiService) diff --git a/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImplTest.kt index 5d15c039..0d703801 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/activitypub/ActivityPubReceiveFollowServiceImplTest.kt @@ -48,13 +48,21 @@ class ActivityPubReceiveFollowServiceImplTest { firstValue.invoke(scheduleContext, ReceiveFollowJob) val actor = scheduleContext.props.props[ReceiveFollowJob.actor.name] val targetActor = scheduleContext.props.props[ReceiveFollowJob.targetActor.name] - val follow = scheduleContext.props.props[ReceiveFollowJob.follow.name] + val follow = scheduleContext.props.props[ReceiveFollowJob.follow.name] as String assertEquals("https://follower.example.com", actor) assertEquals("https://example.com", targetActor) //language=JSON assertEquals( - """{"type":"Follow","name":"Follow","actor":"https://follower.example.com","object":"https://example.com","@context":null}""", - follow + Json.parseToJsonElement( + """{ + "type": "Follow", + "name": "Follow", + "actor": "https://follower.example.com", + "object": "https://example.com", + "@context": null +}""" + ), + Json.parseToJsonElement(follow) ) } } @@ -155,7 +163,14 @@ class ActivityPubReceiveFollowServiceImplTest { data = mapOf( ReceiveFollowJob.actor.name to "https://follower.example.com", ReceiveFollowJob.targetActor.name to "https://example.com", - ReceiveFollowJob.follow.name to """{"type":"Follow","name":"Follow","object":"https://example.com","actor":"https://follower.example.com","@context":null}""" + //language=JSON + ReceiveFollowJob.follow.name to """{ + "type": "Follow", + "name": "Follow", + "object": "https://example.com", + "actor": "https://follower.example.com", + "@context": null +}""" ), json = Json ) diff --git a/vite.config.ts b/vite.config.ts index 3f3c0c58..bf6b9be6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,21 +1,24 @@ -import { defineConfig } from 'vite'; +import {defineConfig, splitVendorChunkPlugin} from 'vite'; import solidPlugin from 'vite-plugin-solid'; import suidPlugin from "@suid/vite-plugin"; +import visualizer from "rollup-plugin-visualizer"; export default defineConfig({ - plugins: [solidPlugin(),suidPlugin()], + plugins: [solidPlugin(),suidPlugin(),splitVendorChunkPlugin()], server: { port: 3000, proxy: { '/api': 'http://localhost:8080', - '/login': 'http://localhost:8080', - '/auth-check': 'http://localhost:8080', - '/refresh-token': 'http://localhost:8080', } }, root: './src/main/web', build: { target: 'esnext', outDir: '../resources/static', + rollupOptions:{ + plugins: [ + visualizer() + ] + } }, });