diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 0fc5a2a09..319678157 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -6,19 +6,7 @@ import { } from 'database' import { Decimal } from 'decimal.js-light' import { GraphQLResolveInfo } from 'graphql' -import { - Arg, - Args, - Authorized, - Ctx, - FieldResolver, - Info, - Int, - Mutation, - Query, - Resolver, - Root, -} from 'type-graphql' +import { Arg, Args, Authorized, Ctx, Info, Int, Mutation, Query, Resolver } from 'type-graphql' import { EntityManager, IsNull, getConnection } from 'typeorm' import { AdminCreateContributionArgs } from '@arg/AdminCreateContributionArgs' @@ -34,7 +22,6 @@ import { Contribution, ContributionListResult } from '@model/Contribution' import { Decay } from '@model/Decay' import { OpenCreation } from '@model/OpenCreation' import { UnconfirmedContribution } from '@model/UnconfirmedContribution' -import { User } from '@model/User' import { RIGHTS } from '@/auth/RIGHTS' import { @@ -61,12 +48,11 @@ import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import { calculateDecay } from '@/util/decay' import { fullName } from '@/util/utilities' -import { ContributionMessage } from '@model/ContributionMessage' +import { start } from 'repl' import { ContributionMessageType } from '../enum/ContributionMessageType' import { loadAllContributions, loadUserContributions } from './util/contributions' import { getOpenCreations, getUserCreation, validateContribution } from './util/creations' -import { extractGraphQLFields, extractGraphQLFieldsForSelect } from './util/extractGraphQLFields' -import { findContributionMessages } from './util/findContributionMessages' +import { extractGraphQLFields } from './util/extractGraphQLFields' import { findContributions } from './util/findContributions' import { getLastTransaction } from './util/getLastTransaction' import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector' @@ -140,20 +126,14 @@ export class ContributionResolver { const res = await contribution.softRemove() return !!res } - @Authorized([RIGHTS.LIST_CONTRIBUTIONS]) @Query(() => ContributionListResult) async listContributions( @Ctx() context: Context, @Arg('pagination') pagination: Paginated, - @Arg('messagePagination', { nullable: true }) messagePagination?: Paginated, ): Promise { const user = getUser(context) - const [dbContributions, count] = await loadUserContributions( - user.id, - pagination, - messagePagination, - ) + const [dbContributions, count] = await loadUserContributions(user.id, pagination) // show contributions in progress first const inProgressContributions = dbContributions.filter( @@ -163,18 +143,9 @@ export class ContributionResolver { (contribution) => contribution.contributionStatus !== ContributionStatus.IN_PROGRESS, ) - return new ContributionListResult( + const result = new ContributionListResult( count, [...inProgressContributions, ...notInProgressContributions].map((contribution) => { - // we currently expect not much contribution messages for needing pagination - // but if we get more than expected, we should get warned - if ((contribution.messages?.length || 0) > (messagePagination?.pageSize || 10)) { - logger.warn('more contribution messages as expected, consider pagination', { - contributionId: contribution.id, - expected: messagePagination?.pageSize || 10, - actual: contribution.messages?.length || 0, - }) - } // filter out moderator messages for this call contribution.messages = contribution.messages?.filter( (message) => @@ -183,6 +154,7 @@ export class ContributionResolver { return contribution }), ) + return result } @Authorized([RIGHTS.LIST_CONTRIBUTIONS]) @@ -625,50 +597,4 @@ export class ContributionResolver { return !!res } - - // Field resolvers - @Authorized([RIGHTS.USER]) - @FieldResolver(() => User, { nullable: true }) - async user( - @Root() contribution: DbContribution, - @Info() info: GraphQLResolveInfo, - ): Promise { - let user: DbUser | null = contribution.user - if (!user) { - const queryBuilder = DbUser.createQueryBuilder('user') - queryBuilder.where('user.id = :userId', { userId: contribution.userId }) - extractGraphQLFieldsForSelect(info, queryBuilder, 'user') - user = await queryBuilder.getOne() - } - if (user) { - return new User(user) - } - return null - } - - @Authorized([RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES]) - @FieldResolver(() => [ContributionMessage], { nullable: true }) - async messages( - @Root() contribution: UnconfirmedContribution, - @Arg('pagination', () => Paginated) pagination: Paginated, - ): Promise { - if (contribution.messagesCount === 0) { - return null - } - const [contributionMessages] = await findContributionMessages({ - contributionId: contribution.id, - pagination, - }) - // we currently expect not much contribution messages for needing pagination - // but if we get more than expected, we should get warned - if (contributionMessages.length > pagination.pageSize) { - logger.warn('more contribution messages as expected, consider pagination', { - contributionId: contribution.id, - expected: pagination.pageSize, - actual: contributionMessages.length, - }) - } - - return contributionMessages.map((message) => new ContributionMessage(message)) - } } diff --git a/backend/src/graphql/resolver/util/contributions.ts b/backend/src/graphql/resolver/util/contributions.ts index 13721744f..58e0914ba 100644 --- a/backend/src/graphql/resolver/util/contributions.ts +++ b/backend/src/graphql/resolver/util/contributions.ts @@ -1,6 +1,7 @@ +import { Order } from '@/graphql/enum/Order' import { Paginated } from '@arg/Paginated' import { Contribution as DbContribution } from 'database' -import { FindManyOptions } from 'typeorm' +import { FindManyOptions, In } from 'typeorm' // TODO: combine with Pagination class for all queries to use function buildPaginationOptions(paginated: Paginated): FindManyOptions { @@ -19,17 +20,41 @@ function buildPaginationOptions(paginated: Paginated): FindManyOptions => { const { order } = paginated - const { order: messageOrder } = messagePagination || { order: 'ASC' } + // manual, faster and simpler queries as auto generated from typeorm + const countPromise = DbContribution.count({ + select: { id: true }, + where: { userId }, + withDeleted: true, + }) + // we collect all contributions, ignoring if user exist or not + const contributionIds = await DbContribution.find({ + select: { id: true }, + where: { userId }, + withDeleted: true, + order: { createdAt: order, id: order }, + ...buildPaginationOptions(paginated), + }) + const contributionsPromise = DbContribution.find({ + relations: { messages: { user: true } }, + withDeleted: true, + order: { createdAt: order, id: order, messages: { createdAt: Order.ASC } }, + where: { id: In(contributionIds.map((c) => c.id)) }, + }) + return [await contributionsPromise, await countPromise] + + // original code + // note: typeorm will create similar queries as above, but more complex and slower + /* return DbContribution.findAndCount({ where: { userId }, withDeleted: true, relations: { messages: { user: true } }, - order: { createdAt: order, id: order, messages: { createdAt: messageOrder } }, + order: { createdAt: order, id: order, messages: { createdAt: Order.ASC } }, ...buildPaginationOptions(paginated), }) + */ } /* @@ -40,9 +65,29 @@ export const loadAllContributions = async ( paginated: Paginated, ): Promise<[DbContribution[], number]> => { const { order } = paginated + // manual, faster queries as auto generated from typeorm + const countPromise = DbContribution.count({ select: { id: true } }) + // console.log('loadAllContributions', { count }) + + const contributionIds = await DbContribution.find({ + select: { id: true }, + order: { createdAt: order, id: order }, + ...buildPaginationOptions(paginated), + }) + const contributionsPromise = DbContribution.find({ + relations: { user: { emailContact: true } }, + order: { createdAt: order, id: order }, + where: { id: In(contributionIds.map((c) => c.id)) }, + }) + return [await contributionsPromise, await countPromise] + + // original code + // note: typeorm will create similar queries as above, but more complex and slower + /* return DbContribution.findAndCount({ relations: { user: { emailContact: true } }, order: { createdAt: order, id: order }, ...buildPaginationOptions(paginated), }) + */ } diff --git a/backend/src/typeorm/connection.ts b/backend/src/typeorm/connection.ts index c2e2e5558..d56a95778 100644 --- a/backend/src/typeorm/connection.ts +++ b/backend/src/typeorm/connection.ts @@ -39,7 +39,7 @@ export class Connection { logging: true, logger: new FileLogger('all', { // workaround to let previous path working, because with esbuild the script root path has changed - logPath: '../' + CONFIG.TYPEORM_LOGGING_RELATIVE_PATH, + logPath: (CONFIG.PRODUCTION ? '../' : '') + CONFIG.TYPEORM_LOGGING_RELATIVE_PATH, }), extra: { charset: 'utf8mb4_unicode_ci', diff --git a/frontend/src/components/Contributions/ContributionList.vue b/frontend/src/components/Contributions/ContributionList.vue index 6badabbf9..3abee7056 100644 --- a/frontend/src/components/Contributions/ContributionList.vue +++ b/frontend/src/components/Contributions/ContributionList.vue @@ -52,24 +52,19 @@ const pollInterval = CONFIG.AUTO_POLL_INTERVAL || undefined const emit = defineEmits(['update-contribution-form']) // refs -const currentPage = ref(1) +const currentPage = ref(Number(route.params.page) || 1) const openMessagesListId = ref(null) // queries const { result, loading, refetch, onResult } = useQuery( listContributions, - { + () => ({ pagination: { currentPage: currentPage.value, pageSize, order: 'DESC', }, - messagePagination: { - currentPage: 1, - pageSize: 10, - order: 'ASC', - }, - }, + }), { fetchPolicy: 'cache-and-network', pollInterval, diff --git a/frontend/src/components/Contributions/ContributionListAll.vue b/frontend/src/components/Contributions/ContributionListAll.vue index 6e1f1a849..c519d0d27 100644 --- a/frontend/src/components/Contributions/ContributionListAll.vue +++ b/frontend/src/components/Contributions/ContributionListAll.vue @@ -29,23 +29,26 @@ import { useQuery } from '@vue/apollo-composable' import CONFIG from '@/config' import PaginatorRouteParamsPage from '@/components/PaginatorRouteParamsPage.vue' import { PAGE_SIZE } from '@/constants' +import { useRoute } from 'vue-router' + +const route = useRoute() // constants const pollInterval = CONFIG.AUTO_POLL_INTERVAL || undefined const pageSize = PAGE_SIZE // computed -const currentPage = ref(1) +const currentPage = ref(Number(route.params.page) || 1) const { result, loading } = useQuery( listAllContributions, - { + () => ({ pagination: { currentPage: currentPage.value, pageSize, order: 'DESC', }, - }, + }), { fetchPolicy: 'cache-and-network', pollInterval, diff --git a/frontend/src/graphql/contributions.graphql b/frontend/src/graphql/contributions.graphql index 0fe4a46f1..842f742bf 100644 --- a/frontend/src/graphql/contributions.graphql +++ b/frontend/src/graphql/contributions.graphql @@ -31,8 +31,8 @@ fragment contributionMessageFields on ContributionMessage { userId } -query listContributions ($pagination: Paginated!, $messagePagination: Paginated!) { - listContributions(pagination: $pagination, messagePagination: $messagePagination) { +query listContributions ($pagination: Paginated!) { + listContributions(pagination: $pagination) { contributionCount contributionList { id @@ -41,7 +41,7 @@ query listContributions ($pagination: Paginated!, $messagePagination: Paginated! contributionDate contributionStatus messagesCount - messages(pagination: $messagePagination) { + messages { ...contributionMessageFields } updatedBy