diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index d4a5e6cf6..1e1a0eb38 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -1,4 +1,4 @@ -import { Context, getUser } from '@/server/context' +import { Context, getUser, getClientTimezoneOffset } from '@/server/context' import { backendLogger as logger } from '@/server/logger' import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx, Int } from 'type-graphql' import { @@ -87,7 +87,9 @@ export class AdminResolver { async searchUsers( @Args() { searchText, currentPage = 1, pageSize = 25, filters }: SearchUsersArgs, + @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) const userRepository = getCustomRepository(UserRepository) const userFields = [ 'id', @@ -115,7 +117,10 @@ export class AdminResolver { } } - const creations = await getUserCreations(users.map((u) => u.id)) + const creations = await getUserCreations( + users.map((u) => u.id), + clientTimezoneOffset, + ) const adminUsers = await Promise.all( users.map(async (user) => { @@ -238,6 +243,7 @@ export class AdminResolver { logger.info( `adminCreateContribution(email=${email}, amount=${amount}, memo=${memo}, creationDate=${creationDate})`, ) + const clientTimezoneOffset = getClientTimezoneOffset(context) if (!isValidDateString(creationDate)) { logger.error(`invalid Date for creationDate=${creationDate}`) throw new Error(`invalid Date for creationDate=${creationDate}`) @@ -267,11 +273,11 @@ export class AdminResolver { const event = new Event() const moderator = getUser(context) logger.trace('moderator: ', moderator.id) - const creations = await getUserCreation(emailContact.userId) + const creations = await getUserCreation(emailContact.userId, clientTimezoneOffset) logger.trace('creations:', creations) const creationDateObj = new Date(creationDate) logger.trace('creationDateObj:', creationDateObj) - validateContribution(creations, amount, creationDateObj) + validateContribution(creations, amount, creationDateObj, clientTimezoneOffset) const contribution = DbContribution.create() contribution.userId = emailContact.userId contribution.amount = amount @@ -294,7 +300,7 @@ export class AdminResolver { event.setEventAdminContributionCreate(eventAdminCreateContribution), ) - return getUserCreation(emailContact.userId) + return getUserCreation(emailContact.userId, clientTimezoneOffset) } @Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTIONS]) @@ -330,6 +336,7 @@ export class AdminResolver { @Args() { id, email, amount, memo, creationDate }: AdminUpdateContributionArgs, @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) const emailContact = await UserContact.findOne({ where: { email }, withDeleted: true, @@ -370,7 +377,7 @@ export class AdminResolver { } const creationDateObj = new Date(creationDate) - let creations = await getUserCreation(user.id) + let creations = await getUserCreation(user.id, clientTimezoneOffset) if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) { creations = updateCreations(creations, contributionToUpdate) @@ -380,7 +387,7 @@ export class AdminResolver { } // all possible cases not to be true are thrown in this function - validateContribution(creations, amount, creationDateObj) + validateContribution(creations, amount, creationDateObj, clientTimezoneOffset) contributionToUpdate.amount = amount contributionToUpdate.memo = memo contributionToUpdate.contributionDate = new Date(creationDate) @@ -394,7 +401,7 @@ export class AdminResolver { result.memo = contributionToUpdate.memo result.date = contributionToUpdate.contributionDate - result.creation = await getUserCreation(user.id) + result.creation = await getUserCreation(user.id, clientTimezoneOffset) const event = new Event() const eventAdminContributionUpdate = new EventAdminContributionUpdate() @@ -410,7 +417,8 @@ export class AdminResolver { @Authorized([RIGHTS.LIST_UNCONFIRMED_CONTRIBUTIONS]) @Query(() => [UnconfirmedContribution]) - async listUnconfirmedContributions(): Promise { + async listUnconfirmedContributions(@Ctx() context: Context): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) const contributions = await getConnection() .createQueryBuilder() .select('c') @@ -424,7 +432,7 @@ export class AdminResolver { } const userIds = contributions.map((p) => p.userId) - const userCreations = await getUserCreations(userIds) + const userCreations = await getUserCreations(userIds, clientTimezoneOffset) const users = await dbUser.find({ where: { id: In(userIds) }, withDeleted: true, @@ -498,6 +506,7 @@ export class AdminResolver { @Arg('id', () => Int) id: number, @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) const contribution = await DbContribution.findOne(id) if (!contribution) { logger.error(`Contribution not found for given id: ${id}`) @@ -516,8 +525,13 @@ export class AdminResolver { logger.error('This user was deleted. Cannot confirm a contribution.') throw new Error('This user was deleted. Cannot confirm a contribution.') } - const creations = await getUserCreation(contribution.userId, false) - validateContribution(creations, contribution.amount, contribution.contributionDate) + const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false) + validateContribution( + creations, + contribution.amount, + contribution.contributionDate, + clientTimezoneOffset, + ) const receivedCallDate = new Date() diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index a061304b7..ef9f83f61 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -1,5 +1,5 @@ import { RIGHTS } from '@/auth/RIGHTS' -import { Context, getUser } from '@/server/context' +import { Context, getUser, getClientTimezoneOffset } from '@/server/context' import { backendLogger as logger } from '@/server/logger' import { Contribution as dbContribution } from '@entity/Contribution' import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql' @@ -31,6 +31,7 @@ export class ContributionResolver { @Args() { amount, memo, creationDate }: ContributionArgs, @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) if (memo.length > MEMO_MAX_CHARS) { logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) @@ -44,10 +45,10 @@ export class ContributionResolver { const event = new Event() const user = getUser(context) - const creations = await getUserCreation(user.id) + const creations = await getUserCreation(user.id, clientTimezoneOffset) logger.trace('creations', creations) const creationDateObj = new Date(creationDate) - validateContribution(creations, amount, creationDateObj) + validateContribution(creations, amount, creationDateObj, clientTimezoneOffset) const contribution = dbContribution.create() contribution.userId = user.id @@ -171,6 +172,7 @@ export class ContributionResolver { @Args() { amount, memo, creationDate }: ContributionArgs, @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) if (memo.length > MEMO_MAX_CHARS) { logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) @@ -206,7 +208,7 @@ export class ContributionResolver { ) } const creationDateObj = new Date(creationDate) - let creations = await getUserCreation(user.id) + let creations = await getUserCreation(user.id, clientTimezoneOffset) if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) { creations = updateCreations(creations, contributionToUpdate) } else { @@ -215,7 +217,7 @@ export class ContributionResolver { } // all possible cases not to be true are thrown in this function - validateContribution(creations, amount, creationDateObj) + validateContribution(creations, amount, creationDateObj, clientTimezoneOffset) const contributionMessage = ContributionMessage.create() contributionMessage.contributionId = contributionId diff --git a/backend/src/graphql/resolver/util/creations.test.ts b/backend/src/graphql/resolver/util/creations.test.ts index 601d73027..104ba5fe9 100644 --- a/backend/src/graphql/resolver/util/creations.test.ts +++ b/backend/src/graphql/resolver/util/creations.test.ts @@ -159,7 +159,7 @@ describe('util/creation', () => { describe('call getUserCreation now', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id)).resolves.toEqual([ + await expect(getUserCreation(user.id, 0)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), @@ -188,7 +188,7 @@ describe('util/creation', () => { describe('call getUserCreation with UTC', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id)).resolves.toEqual([ + await expect(getUserCreation(user.id, 0)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), @@ -198,7 +198,7 @@ describe('util/creation', () => { describe('call getUserCreation with JST (GMT+0900)', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, -540)).resolves.toEqual([ + await expect(getUserCreation(user.id, -540, true)).resolves.toEqual([ expect.decimalEqual(340), expect.decimalEqual(350), expect.decimalEqual(1000), @@ -208,7 +208,7 @@ describe('util/creation', () => { describe('call getUserCreation with PST (GMT-0800)', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, 480)).resolves.toEqual([ + await expect(getUserCreation(user.id, 480, true)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), @@ -234,7 +234,7 @@ describe('util/creation', () => { describe('call getUserCreation with UTC', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, -540)).resolves.toEqual([ + await expect(getUserCreation(user.id, -540, true)).resolves.toEqual([ expect.decimalEqual(340), expect.decimalEqual(350), expect.decimalEqual(1000), @@ -244,7 +244,7 @@ describe('util/creation', () => { describe('call getUserCreation with JST (GMT+0900)', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, -540)).resolves.toEqual([ + await expect(getUserCreation(user.id, -540, true)).resolves.toEqual([ expect.decimalEqual(340), expect.decimalEqual(350), expect.decimalEqual(1000), @@ -254,7 +254,7 @@ describe('util/creation', () => { describe('call getUserCreation with PST (GMT-0800)', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, 450)).resolves.toEqual([ + await expect(getUserCreation(user.id, 450, true)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index decf4de27..efd0ea3b0 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -38,8 +38,8 @@ export const validateContribution = ( export const getUserCreations = async ( ids: number[], - includePending = true, timezoneOffset = 0, + includePending = true, ): Promise => { logger.trace('getUserCreations:', ids, includePending) const months = getCreationMonths(timezoneOffset) @@ -91,11 +91,11 @@ export const getUserCreations = async ( export const getUserCreation = async ( id: number, - includePending = true, timezoneOffset = 0, + includePending = true, ): Promise => { logger.trace('getUserCreation', id, includePending, timezoneOffset) - const creations = await getUserCreations([id], includePending, timezoneOffset) + const creations = await getUserCreations([id], timezoneOffset, includePending) logger.trace('getUserCreation creations=', creations) return creations[0] ? creations[0].creations : FULL_CREATION_AVAILABLE }