Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Button-based pagination #304

Merged
merged 3 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/src/main/kotlin/com/gmtkgamejam/models/posts/dtos/PostsDTO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.gmtkgamejam.models.posts.dtos

import com.gmtkgamejam.models.posts.PostItem
import kotlinx.serialization.Serializable

@Serializable
data class PostsDTO(
val posts: List<PostItem>,
val pagination: Map<String, Int>
)
16 changes: 13 additions & 3 deletions api/src/main/kotlin/com/gmtkgamejam/repositories/PostRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ import java.time.format.DateTimeFormatter

interface PostRepository {
fun createPost(postItem: PostItem)
fun getPosts(filter: Bson, sort: Bson): List<PostItem>
fun getPosts(filter: Bson, sort: Bson, page: Int): List<PostItem>
fun getPost(id: String) : PostItem?
fun getPostByAuthorId(authorId: String, ignoreDeletion: Boolean = false) : PostItem?
fun updatePost(postItem: PostItem)
fun deletePost(postItem: PostItem)
fun addQueryView(postItem: PostItem)
fun addFullPageView(postItem: PostItem)
fun getPostCount(): Int

companion object {
const val PAGE_SIZE = 36
}
}

open class PostRepositoryImpl(val client: MongoClient) : PostRepository {
Expand All @@ -39,8 +44,9 @@ open class PostRepositoryImpl(val client: MongoClient) : PostRepository {
}

// Un-paginated version should be used for Admin endpoints
override fun getPosts(filter: Bson, sort: Bson): List<PostItem> {
return col.find(filter).sort(sort).toList()
override fun getPosts(filter: Bson, sort: Bson, page: Int): List<PostItem> {
val pageSize = PostRepository.PAGE_SIZE
return col.find(filter).sort(sort).limit(pageSize).skip(pageSize * (page - 1)).toList()
}

override fun getPost(id: String) : PostItem? {
Expand All @@ -56,6 +62,10 @@ open class PostRepositoryImpl(val client: MongoClient) : PostRepository {
return col.findOne(filter)
}

override fun getPostCount(): Int {
return col.countDocuments(PostItem::deletedAt eq null).toInt();
}

override fun updatePost(postItem: PostItem) {
if (bannedUsersCol.findOne(BannedUser::discordId eq postItem.authorId) != null) {
throw Exception("User is banned, cannot perform action!")
Expand Down
6 changes: 3 additions & 3 deletions api/src/main/kotlin/com/gmtkgamejam/routing/AdminRoutes.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.gmtkgamejam.routing

import com.gmtkgamejam.models.admin.dtos.BanUnbanUserDto
import com.gmtkgamejam.models.admin.BannedUser
import com.gmtkgamejam.models.posts.PostItem
import com.gmtkgamejam.models.admin.dtos.BanUnbanUserDto
import com.gmtkgamejam.models.admin.dtos.DeletePostDto
import com.gmtkgamejam.models.admin.dtos.ReportedUsersClearDto
import com.gmtkgamejam.models.posts.PostItem
import com.gmtkgamejam.respondJSON
import com.gmtkgamejam.services.AdminService
import com.gmtkgamejam.services.PostService
Expand All @@ -30,7 +30,7 @@ fun Application.configureAdminRouting() {
route("/reports") {
get {
val filters = mutableListOf(PostItem::deletedAt eq null, PostItem::reportCount gt 0)
call.respond(service.getPosts(and(filters), descending(PostItem::reportCount)))
call.respond(service.getPosts(and(filters), descending(PostItem::reportCount), 1))
}
post("/clear") {
val data = call.receive<ReportedUsersClearDto>()
Expand Down
29 changes: 22 additions & 7 deletions api/src/main/kotlin/com/gmtkgamejam/routing/PostRoutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import com.gmtkgamejam.models.posts.Availability
import com.gmtkgamejam.models.posts.PostItem
import com.gmtkgamejam.models.posts.Skills
import com.gmtkgamejam.models.posts.Tools
import com.gmtkgamejam.models.posts.dtos.PostItemCreateDto
import com.gmtkgamejam.models.posts.dtos.PostItemReportDto
import com.gmtkgamejam.models.posts.dtos.PostItemUnableToContactReportDto
import com.gmtkgamejam.models.posts.dtos.PostItemUpdateDto
import com.gmtkgamejam.models.posts.dtos.*
import com.gmtkgamejam.repositories.PostRepository
import com.gmtkgamejam.respondJSON
import com.gmtkgamejam.services.AnalyticsService
import com.gmtkgamejam.services.AuthService
Expand All @@ -27,6 +25,7 @@ import org.bson.conversions.Bson
import org.litote.kmongo.*
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlin.math.ceil
import kotlin.math.min
import kotlin.reflect.full.memberProperties
import kotlin.text.Regex.Companion.escape
Expand All @@ -42,8 +41,13 @@ fun Application.configurePostRouting() {
route("/posts") {
get {
val params = call.parameters
val page = params["page"]?.toInt() ?: 1

val posts = service.getPosts(and(getFilterFromParameters(params)), getSortFromParameters(params))
val posts = service.getPosts(
and(getFilterFromParameters(params)),
getSortFromParameters(params),
page
)

// Set isFavourite on posts for this user if they're logged in
call.request.header("Authorization")?.substring(7)
Expand All @@ -54,7 +58,17 @@ fun Application.configurePostRouting() {
posts.map { it.isFavourite = favouritesList.postIds.contains(it.id) }
}

call.respond(posts)
val pagination = mapOf(
"current" to page,
"total" to ceil(service.getPostCount() / PostRepository.PAGE_SIZE.toDouble()).toInt()
)

call.respond(
PostsDTO(
posts,
pagination
)
)

launch {
analyticsService.trackQuery(params.toMap().toSortedMap())
Expand Down Expand Up @@ -131,7 +145,8 @@ fun Application.configurePostRouting() {
or(favouritesFilters),
and(getFilterFromParameters(params))
),
getSortFromParameters(params)
getSortFromParameters(params),
params["page"]?.toInt() ?: 1
)
posts.map { post -> post.isFavourite = true }

Expand Down
8 changes: 6 additions & 2 deletions api/src/main/kotlin/com/gmtkgamejam/services/PostService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class PostService : KoinComponent {
repository.createPost(postItem)
}

fun getPosts(filter: Bson, sort: Bson): List<PostItem> {
return repository.getPosts(filter, sort)
fun getPosts(filter: Bson, sort: Bson, page: Int): List<PostItem> {
return repository.getPosts(filter, sort, page)
}

fun getPost(id: String) : PostItem? {
Expand All @@ -26,6 +26,10 @@ class PostService : KoinComponent {
return repository.getPostByAuthorId(authorId, ignoreDeletion)
}

fun getPostCount(): Int {
return repository.getPostCount()
}

fun updatePost(postItem: PostItem) {
repository.updatePost(postItem)
}
Expand Down
4 changes: 2 additions & 2 deletions ui/src/api/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from "react-query";
import {
Post,
PostApiResult,
PostResponseDTO,
postFromApiResult,
} from "../common/models/post";
import { useApiRequest } from "./apiRequest";
Expand All @@ -25,7 +25,7 @@ export function useCreateBotDmMutation(
return useMutation({
...opts,
mutationFn: async (variables) => {
const result = await apiRequest<PostApiResult>("/bot/dm", {
const result = await apiRequest<PostResponseDTO>("/bot/dm", {
method: "POST",
body: variables,
});
Expand Down
12 changes: 6 additions & 6 deletions ui/src/api/myPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "react-query";
import {
Post,
PostApiResult,
PostResponseDTO,
postFromApiResult,
} from "../common/models/post";
import { expectNotFound, useApiRequest } from "./apiRequest";
Expand All @@ -32,7 +32,7 @@ export function useMyPostQuery(
return useQuery(
MY_POST_QUERY_KEY,
() =>
expectNotFound(apiRequest<PostApiResult>("/posts/mine")).then(
expectNotFound(apiRequest<PostResponseDTO>("/posts/mine")).then(
(result) => result && postFromApiResult(result)
),
{
Expand Down Expand Up @@ -62,21 +62,21 @@ export function useMyPostMutation(
return useMutation({
...opts,
mutationFn: async (variables) => {
const existing = await queryClient.fetchQuery<PostApiResult>(
const existing = await queryClient.fetchQuery<PostResponseDTO>(
MY_POST_QUERY_KEY
);
let result;

if (existing) {
result = await apiRequest<PostApiResult>("/posts/mine", {
result = await apiRequest<PostResponseDTO>("/posts/mine", {
method: "PUT",
body: {
...variables,
author: userInfo.data?.username,
},
});
} else {
result = await apiRequest<PostApiResult>("/posts", {
result = await apiRequest<PostResponseDTO>("/posts", {
method: "POST",
body: {
...variables,
Expand Down Expand Up @@ -106,7 +106,7 @@ export function useDeleteMyPostMutation(
return useMutation({
...opts,
mutationFn: async (variables) => {
const result = await apiRequest<PostApiResult>("/posts/mine", {
const result = await apiRequest<PostResponseDTO>("/posts/mine", {
method: "DELETE",
body: variables,
});
Expand Down
15 changes: 10 additions & 5 deletions ui/src/api/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import {
UseQueryResult
} from 'react-query';
import {useApiRequest} from "./apiRequest.ts";
import {Post, PostApiResult, postFromApiResult} from "../common/models/post.ts";
import {Post, PostResponseDTO, postFromApiResult, PostResponse, PostDTO} from '../common/models/post.ts';
import {useAuth} from './AuthContext.tsx';
import {useSearchParams} from 'react-router-dom';

export function usePosts(): UseQueryResult<Post[], Error> {
export function usePosts(): UseQueryResult<PostResponse, Error> {
const [searchParams, _] = useSearchParams();
const { token } = useAuth() ?? {};
const apiRequest = useApiRequest();
Expand All @@ -23,10 +23,15 @@ export function usePosts(): UseQueryResult<Post[], Error> {
return useQuery(
["posts", "list", searchParams.toString() ?? ""],
() => {
return apiRequest<PostApiResult[]>(url, {method: "GET", authToken: token});
return apiRequest<PostResponseDTO>(url, {method: "GET", authToken: token});
},
{
select: (posts: PostApiResult[]) => posts.map(postFromApiResult),
select: (result: PostResponseDTO) => {
return {
posts: result.posts.map(postFromApiResult),
pagination: result.pagination
}
},
}
);
}
Expand Down Expand Up @@ -105,7 +110,7 @@ export function useFavouritePostMutation(
const method = variables.isFavourite ? "POST" : "DELETE";
delete variables.isFavourite; // Don't submit this field, it's only used in the UI

const result = await apiRequest<PostApiResult>("/favourites", {
const result = await apiRequest<PostDTO>("/favourites", {
method: method,
body: variables,
});
Expand Down
14 changes: 12 additions & 2 deletions ui/src/common/models/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,23 @@ export interface Post {
deletedAt?: Date;
}

export type PostApiResult = Omit<Post, "createdAt" | "updatedAt" | "deletedAt"> & {
export type PostDTO = Omit<Post, "createdAt" | "updatedAt" | "deletedAt"> & {
createdAt: string;
updatedAt?: string;
deletedAt?: string;
};

export function postFromApiResult(input: PostApiResult): Post {
export type PostResponseDTO = {
posts: PostDTO[];
pagination: {current: number, total: number};
}

export type PostResponse = {
posts: Post[];
pagination: {current: number, total: number};
}

export function postFromApiResult(input: PostDTO): Post {
return {
...input,
createdAt: new Date(input.createdAt),
Expand Down
Loading
Loading