From b1d4ea3b99308d245431589deb508934ae39c9ff Mon Sep 17 00:00:00 2001 From: WillDotWhite Date: Tue, 9 Jan 2024 21:50:26 +0000 Subject: [PATCH] Working form submission and basic validation --- .../kotlin/com/gmtkgamejam/Application.kt | 1 + .../models/posts/dtos/PostItemUpdateDto.kt | 19 +++++++- .../com/gmtkgamejam/routing/PostRoutes.kt | 2 +- .../com/gmtkgamejam/routing/UserInfoRoutes.kt | 26 ++++++++++- ui2/src/api/myPost.ts | 6 +-- ui2/src/common/models/post.ts | 2 +- ui2/src/pages/mypost/MyPost.tsx | 29 ++++++++---- ui2/src/pages/mypost/MyPostWrapper.tsx | 46 +++++++++++++++---- 8 files changed, 105 insertions(+), 26 deletions(-) diff --git a/api/src/main/kotlin/com/gmtkgamejam/Application.kt b/api/src/main/kotlin/com/gmtkgamejam/Application.kt index 55e1221e..f7f66cf6 100644 --- a/api/src/main/kotlin/com/gmtkgamejam/Application.kt +++ b/api/src/main/kotlin/com/gmtkgamejam/Application.kt @@ -36,6 +36,7 @@ fun Application.configureRequestHandling() { json(Json { prettyPrint = true isLenient = true + ignoreUnknownKeys = true }) } diff --git a/api/src/main/kotlin/com/gmtkgamejam/models/posts/dtos/PostItemUpdateDto.kt b/api/src/main/kotlin/com/gmtkgamejam/models/posts/dtos/PostItemUpdateDto.kt index fc70b5c1..78ff3f70 100644 --- a/api/src/main/kotlin/com/gmtkgamejam/models/posts/dtos/PostItemUpdateDto.kt +++ b/api/src/main/kotlin/com/gmtkgamejam/models/posts/dtos/PostItemUpdateDto.kt @@ -19,4 +19,21 @@ data class PostItemUpdateDto ( var availability: Availability?, var timezoneOffsets: Set?, var languages: Set?, -) \ No newline at end of file +) + +//author: "dotwo" +//authorId: "427486675409829898" +//availability: "MINIMAL" +//createdAt: "2024-01-09T20:56:06.000Z" +//deletedAt: null +//description: "" +//id: "3555550028524498482" +//languages: ["en"] +//preferredTools: [] +//reportCount: 0 +//size: 1 +//skillsPossessed: ["ART_2D", "CODE"] +//skillsSought: ["ART_3D"] +//timezoneOffsets: [1] +//unableToContactCount: 0 +//updatedAt: "2024-01-09T20:56:06.000Z" \ No newline at end of file diff --git a/api/src/main/kotlin/com/gmtkgamejam/routing/PostRoutes.kt b/api/src/main/kotlin/com/gmtkgamejam/routing/PostRoutes.kt index 944ded26..f811e9d6 100644 --- a/api/src/main/kotlin/com/gmtkgamejam/routing/PostRoutes.kt +++ b/api/src/main/kotlin/com/gmtkgamejam/routing/PostRoutes.kt @@ -227,7 +227,7 @@ fun Application.configurePostRouting() { it.preferredTools = data.preferredTools ?: it.preferredTools it.languages = data.languages ?: it.languages it.availability = data.availability ?: it.availability - it.updatedAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + it.updatedAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) it.timezoneOffsets = (data.timezoneOffsets ?: it.timezoneOffsets).filter { tz -> tz >= -12 && tz <= 12 } .toSet() diff --git a/api/src/main/kotlin/com/gmtkgamejam/routing/UserInfoRoutes.kt b/api/src/main/kotlin/com/gmtkgamejam/routing/UserInfoRoutes.kt index a704ba77..302ca556 100644 --- a/api/src/main/kotlin/com/gmtkgamejam/routing/UserInfoRoutes.kt +++ b/api/src/main/kotlin/com/gmtkgamejam/routing/UserInfoRoutes.kt @@ -16,8 +16,11 @@ import io.ktor.server.routing.* import org.koin.ktor.ext.inject import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.time.LocalDateTime import java.util.* +typealias UserId = String + fun Application.configureUserInfoRouting() { val logger: Logger = LoggerFactory.getLogger(javaClass) @@ -25,6 +28,8 @@ fun Application.configureUserInfoRouting() { val bot: DiscordBot by inject() val service = AuthService() + val shortLiveCache: MutableMap> = mutableMapOf() + routing { authenticate("auth-jwt") { // These routes go through the authentication middleware defined in Auth.kt get("/hello") { @@ -36,11 +41,27 @@ fun Application.configureUserInfoRouting() { } get("/userinfo") { + val currentTime = LocalDateTime.now() val principal = call.principal() val id = principal?.payload?.getClaim("id")?.asString() service.getTokenSet(id!!)?.let { val tokenSet = it + + // Very short TTL cache to avoid unnecessary traffic for quick turnaround behaviour + // We don't expect the cache to expire during regular traffic + if (shortLiveCache.containsKey(tokenSet.discordId)) { + val (cacheSetTime, userInfo) = shortLiveCache[tokenSet.discordId]!! + shortLiveCache.remove(tokenSet.discordId) + + // If the cache set was less than 5 minutes ago, don't hit discord again + if (currentTime < cacheSetTime.plusMinutes(5L)) { + // Refresh cache entry + shortLiveCache[userInfo.id] = Pair(LocalDateTime.now(), userInfo) + return@get call.respond(userInfo) + } + } + var accessToken = tokenSet.accessToken // If access token has expired, try a dirty inline refresh @@ -65,8 +86,9 @@ fun Application.configureUserInfoRouting() { val hasPermissions = bot.doesUserHaveValidPermissions(user.id) val isUserInGuild = bot.isUserInGuild(user.id) - val userinfo = UserInfo(user, displayName, isUserInGuild, hasPermissions) - return@get call.respond(userinfo) + val userInfo = UserInfo(user, displayName, isUserInGuild, hasPermissions) + shortLiveCache[user.id] = Pair(LocalDateTime.now(), userInfo) + return@get call.respond(userInfo) } call.respondJSON("Couldn't load token set from DB", status = HttpStatusCode.NotFound) diff --git a/ui2/src/api/myPost.ts b/ui2/src/api/myPost.ts index 24c5d131..8bbf4d53 100644 --- a/ui2/src/api/myPost.ts +++ b/ui2/src/api/myPost.ts @@ -67,10 +67,6 @@ export function useMyPostMutation( ); let result; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - variables.timezoneOffsets = variables.timezoneOffsets.map(str => timezoneOffsetToInt(str)) - if (existing) { result = await apiRequest("/posts/mine", { method: "PUT", @@ -100,7 +96,7 @@ export function useMyPostMutation( } export interface DeleteMyPostMutationVariables { - id: string; + postId: string; } export function useDeleteMyPostMutation( opts?: UseMutationOptions diff --git a/ui2/src/common/models/post.ts b/ui2/src/common/models/post.ts index 6bb5b5fc..c8619b33 100644 --- a/ui2/src/common/models/post.ts +++ b/ui2/src/common/models/post.ts @@ -8,7 +8,7 @@ export interface Post { skillsSought: string[]; preferredTools: string[]; availability: string; - timezoneOffsets: number[]; + timezoneOffsets: string[]; languages: string[]; isFavourite: boolean; reportCount: number; diff --git a/ui2/src/pages/mypost/MyPost.tsx b/ui2/src/pages/mypost/MyPost.tsx index 61e18018..3d6a62bb 100644 --- a/ui2/src/pages/mypost/MyPost.tsx +++ b/ui2/src/pages/mypost/MyPost.tsx @@ -8,7 +8,17 @@ import {tools} from "../../common/models/engines.tsx"; import {timezones} from "../../common/models/timezones.ts"; import {Post} from "../../common/models/post.ts"; -export const MyPost: React.FC<{params: FormikProps}> = ({params}) => { +export const MyPost: React.FC<{ + params: FormikProps, + author: string, + authorId: string, + hasPost: boolean +}> = ({ + params, + author, + authorId, + hasPost +}) => { const {values, submitForm} = params; @@ -16,6 +26,10 @@ export const MyPost: React.FC<{params: FormikProps}> = ({params}) => {
+ {/* This is jank, improve this later with a look up on submit */} + + +
@@ -32,14 +46,13 @@ export const MyPost: React.FC<{params: FormikProps}> = ({params}) => { ) @@ -114,9 +127,9 @@ const FieldLanguages: React.FC = () => { const FieldTools: React.FC = () => { return (
- + { const FieldTimezones: React.FC = () => { return (
- + { useEnsureLoggedIn(); + const userInfo = useUserInfo(); const myPostQuery = useMyPostQuery(); const post = myPostQuery?.data as Post; - const initialValues: Post = { - description: "", - languages: ["en"] + const initialValues: Post = post ? {...post, timezoneOffsets: post?.timezoneOffsets.map(i => i.toString())} : defaultFormValues + + const onValidateForm = (values: Post) => { + const errors = [] + // @ts-ignore + if (!values.description) errors.push("A description is required") + + return errors } const onSubmitForm = (values: any) => { + const errors = onValidateForm(values) + if (errors.length > 0) { + errors.map(error => toast.error(error)) + return + } + save(values) console.log(values) } @@ -33,9 +57,15 @@ export const MyPostWrapper: React.FC = () => { setTimeout(() => window.location.reload(), 200); } - const { mutate: save, isLoading: isSaving } = useMyPostMutation({onSuccess: onSubmitSuccess}); + const { mutate: save } = useMyPostMutation({onSuccess: onSubmitSuccess}); const deletePostMutation = useDeleteMyPostMutation({onSuccess: onDeleteSuccess}); + /** Ensure user is logged in to view the page; give them enough information to see what's happening */ + if (userInfo?.isLoading || !userInfo.data) {return (

Please wait...

)} + + /** Ensure we have active form data before rendering form */ + if (myPostQuery?.isLoading) {return (<>)} + return (
@@ -46,17 +76,17 @@ export const MyPostWrapper: React.FC = () => { {(params: FormikProps) => ( <>

Create New Post

- + )} - {post && } + {post && deletePostMutation.mutate({ postId: post.id })} />}
) } -const DeletePostButton: React.FC<{postId: string, onClick: any}> = ({postId, onClick}) => { +const DeletePostButton: React.FC<{postId: string, onClickHandler: any}> = ({onClickHandler}) => { return (