From 228451574daa6d1d67c854a4f8b7a6039e2e3b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Fri, 26 Aug 2022 02:28:04 +0200 Subject: [PATCH] adapt backend on database migration of UserContacts --- backend/src/event/Event.ts | 10 ++ backend/src/event/EventProtocolType.ts | 1 + .../graphql/model/UnconfirmedContribution.ts | 2 +- backend/src/graphql/model/User.ts | 12 +- backend/src/graphql/model/UserAdmin.ts | 4 +- backend/src/graphql/model/UserContact.ts | 56 +++++++++ backend/src/graphql/resolver/AdminResolver.ts | 83 +++++++++---- backend/src/graphql/resolver/GdtResolver.ts | 4 +- .../graphql/resolver/TransactionResolver.ts | 21 ++-- backend/src/graphql/resolver/UserResolver.ts | 115 ++++++++++-------- backend/src/typeorm/repository/User.ts | 8 +- backend/src/util/communityUser.ts | 1 + backend/src/webhook/elopage.ts | 4 +- .../0048-add_user_contacts_table/User.ts | 4 +- .../UserContact.ts | 15 ++- .../0048-add_user_contacts_table.ts | 2 +- 16 files changed, 245 insertions(+), 97 deletions(-) create mode 100644 backend/src/graphql/model/UserContact.ts diff --git a/backend/src/event/Event.ts b/backend/src/event/Event.ts index 6f07661f1..85fba896d 100644 --- a/backend/src/event/Event.ts +++ b/backend/src/event/Event.ts @@ -32,6 +32,7 @@ export class EventRegister extends EventBasicUserId {} export class EventRedeemRegister extends EventBasicRedeem {} export class EventInactiveAccount extends EventBasicUserId {} export class EventSendConfirmationEmail extends EventBasicUserId {} +export class EventSendAccountMultiRegistrationEmail extends EventBasicUserId {} export class EventConfirmationEmail extends EventBasicUserId {} export class EventRegisterEmailKlicktipp extends EventBasicUserId {} export class EventLogin extends EventBasicUserId {} @@ -113,6 +114,15 @@ export class Event { return this } + public setEventSendAccountMultiRegistrationEmail( + ev: EventSendAccountMultiRegistrationEmail, + ): Event { + this.setByBasicUser(ev.userId) + this.type = EventProtocolType.SEND_ACCOUNT_MULTI_REGISTRATION_EMAIL + + return this + } + public setEventConfirmationEmail(ev: EventConfirmationEmail): Event { this.setByBasicUser(ev.userId) this.type = EventProtocolType.CONFIRM_EMAIL diff --git a/backend/src/event/EventProtocolType.ts b/backend/src/event/EventProtocolType.ts index 0f61f787a..52bcf8349 100644 --- a/backend/src/event/EventProtocolType.ts +++ b/backend/src/event/EventProtocolType.ts @@ -5,6 +5,7 @@ export enum EventProtocolType { REDEEM_REGISTER = 'REDEEM_REGISTER', INACTIVE_ACCOUNT = 'INACTIVE_ACCOUNT', SEND_CONFIRMATION_EMAIL = 'SEND_CONFIRMATION_EMAIL', + SEND_ACCOUNT_MULTI_REGISTRATION_EMAIL = 'SEND_ACCOUNT_MULTI_REGISTRATION_EMAIL', CONFIRM_EMAIL = 'CONFIRM_EMAIL', REGISTER_EMAIL_KLICKTIPP = 'REGISTER_EMAIL_KLICKTIPP', LOGIN = 'LOGIN', diff --git a/backend/src/graphql/model/UnconfirmedContribution.ts b/backend/src/graphql/model/UnconfirmedContribution.ts index 1d697a971..a81bb4a49 100644 --- a/backend/src/graphql/model/UnconfirmedContribution.ts +++ b/backend/src/graphql/model/UnconfirmedContribution.ts @@ -13,7 +13,7 @@ export class UnconfirmedContribution { this.date = contribution.contributionDate this.firstName = user ? user.firstName : '' this.lastName = user ? user.lastName : '' - this.email = user ? user.email : '' + this.email = user ? user.emailContact.email : '' this.creation = creations } diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 728851ec2..a28fe4b69 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -3,6 +3,7 @@ import { KlickTipp } from './KlickTipp' import { User as dbUser } from '@entity/User' import Decimal from 'decimal.js-light' import { FULL_CREATION_AVAILABLE } from '../resolver/const/const' +import { UserContact } from './UserContact' @ObjectType() export class User { @@ -10,8 +11,9 @@ export class User { this.id = user.id this.gradidoID = user.gradidoID this.alias = user.alias - // this.email = user.email + this.emailId = user.emailId this.email = user.emailContact.email + this.emailContact = user.emailContact this.firstName = user.firstName this.lastName = user.lastName this.deletedAt = user.deletedAt @@ -35,12 +37,18 @@ export class User { gradidoID: string @Field(() => String, { nullable: true }) - alias: string + alias?: string + + @Field(() => Number, { nullable: true }) + emailId: number | null // TODO privacy issue here @Field(() => String) email: string + @Field(() => UserContact) + emailContact: UserContact + @Field(() => String, { nullable: true }) firstName: string | null diff --git a/backend/src/graphql/model/UserAdmin.ts b/backend/src/graphql/model/UserAdmin.ts index cf3663e70..08dc405ac 100644 --- a/backend/src/graphql/model/UserAdmin.ts +++ b/backend/src/graphql/model/UserAdmin.ts @@ -6,11 +6,11 @@ import { User } from '@entity/User' export class UserAdmin { constructor(user: User, creation: Decimal[], hasElopage: boolean, emailConfirmationSend: string) { this.userId = user.id - this.email = user.email + this.email = user.emailContact.email this.firstName = user.firstName this.lastName = user.lastName this.creation = creation - this.emailChecked = user.emailChecked + this.emailChecked = user.emailContact.emailChecked this.hasElopage = hasElopage this.deletedAt = user.deletedAt this.emailConfirmationSend = emailConfirmationSend diff --git a/backend/src/graphql/model/UserContact.ts b/backend/src/graphql/model/UserContact.ts new file mode 100644 index 000000000..902e2f9f2 --- /dev/null +++ b/backend/src/graphql/model/UserContact.ts @@ -0,0 +1,56 @@ +import { ObjectType, Field } from 'type-graphql' +import { UserContact as dbUserCOntact} from '@entity/UserContact' + +@ObjectType() +export class UserContact { + constructor(userContact: dbUserCOntact) { + this.id = userContact.id + this.type = userContact.type + this.userId = userContact.userId + this.email = userContact.email + // this.emailVerificationCode = userContact.emailVerificationCode + this.emailOptInTypeId = userContact.emailOptInTypeId + this.emailResendCount = userContact.emailResendCount + this.emailChecked = userContact.emailChecked + this.phone = userContact.phone + this.createdAt = userContact.createdAt + this.updatedAt = userContact.updatedAt + this.deletedAt = userContact.deletedAt + } + + @Field(() => Number) + id: number + + @Field(() => String) + type: string + + @Field(() => Number) + userId: number + + @Field(() => String) + email: string + + // @Field(() => BigInt, { nullable: true }) + // emailVerificationCode: BigInt | null + + @Field(() => Number, { nullable: true }) + emailOptInTypeId: number | null + + @Field(() => Number, { nullable: true }) + emailResendCount: number | null + + @Field(() => Boolean) + emailChecked: boolean + + @Field(() => String, { nullable: true }) + phone: string | null + + @Field(() => Date) + createdAt: Date + + @Field(() => Date, { nullable: true }) + updatedAt: Date | null + + @Field(() => Date, { nullable: true }) + deletedAt: Date | null +} diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index e70fe71ee..5d283026d 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -44,7 +44,7 @@ import Paginated from '@arg/Paginated' import TransactionLinkFilters from '@arg/TransactionLinkFilters' import { Order } from '@enum/Order' import { communityUser } from '@/util/communityUser' -import { checkOptInCode, activationLink, printTimeDuration } from './UserResolver' +import { checkEmailVerificationCode, activationLink, printTimeDuration } from './UserResolver' import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver' import CONFIG from '@/config' @@ -62,6 +62,7 @@ import { MEMO_MAX_CHARS, MEMO_MIN_CHARS, } from './const/const' +import { UserContact } from '@entity/UserContact' // const EMAIL_OPT_IN_REGISTER = 1 // const EMAIL_OPT_UNKNOWN = 3 // elopage? @@ -118,7 +119,8 @@ export class AdminResolver { const adminUsers = await Promise.all( users.map(async (user) => { let emailConfirmationSend = '' - if (!user.emailChecked) { + if (!user.emailContact.emailChecked) { + /* const emailOptIn = await LoginEmailOptIn.findOne( { userId: user.id, @@ -138,12 +140,18 @@ export class AdminResolver { emailConfirmationSend = emailOptIn.createdAt.toISOString() } } + */ + if (user.emailContact.updatedAt) { + emailConfirmationSend = user.emailContact.updatedAt.toISOString() + } else { + emailConfirmationSend = user.emailContact.createdAt.toISOString() + } } const userCreations = creations.find((c) => c.id === user.id) const adminUser = new UserAdmin( user, userCreations ? userCreations.creations : FULL_CREATION_AVAILABLE, - await hasElopageBuys(user.email), + await hasElopageBuys(user.emailContact.email), emailConfirmationSend, ) return adminUser @@ -239,24 +247,27 @@ export class AdminResolver { @Args() { email, amount, memo, creationDate }: AdminCreateContributionArgs, @Ctx() context: Context, ): Promise { - const user = await dbUser.findOne({ email }, { withDeleted: true }) - if (!user) { + const emailContact = await UserContact.findOne({ email }, { withDeleted: true }) + if (!emailContact) { + logger.error(`Could not find user with email: ${email}`) throw new Error(`Could not find user with email: ${email}`) } - if (user.deletedAt) { - throw new Error('This user was deleted. Cannot create a contribution.') + if (emailContact.deletedAt) { + logger.error('This emailContact was deleted. Cannot create a contribution.') + throw new Error('This emailContact was deleted. Cannot create a contribution.') } - if (!user.emailChecked) { + if (!emailContact.emailChecked) { + logger.error('Contribution could not be saved, Email is not activated') throw new Error('Contribution could not be saved, Email is not activated') } const moderator = getUser(context) logger.trace('moderator: ', moderator.id) - const creations = await getUserCreation(user.id) + const creations = await getUserCreation(emailContact.userId) logger.trace('creations', creations) const creationDateObj = new Date(creationDate) validateContribution(creations, amount, creationDateObj) const contribution = Contribution.create() - contribution.userId = user.id + contribution.userId = emailContact.userId contribution.amount = amount contribution.createdAt = new Date() contribution.contributionDate = creationDateObj @@ -267,7 +278,7 @@ export class AdminResolver { logger.trace('contribution to save', contribution) await Contribution.save(contribution) - return getUserCreation(user.id) + return getUserCreation(emailContact.userId) } @Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTIONS]) @@ -303,11 +314,18 @@ export class AdminResolver { @Args() { id, email, amount, memo, creationDate }: AdminUpdateContributionArgs, @Ctx() context: Context, ): Promise { - const user = await dbUser.findOne({ email }, { withDeleted: true }) + const emailContact = await UserContact.findOne({ email }, { withDeleted: true }) + if (!emailContact) { + logger.error(`Could not find UserContact with email: ${email}`) + throw new Error(`Could not find UserContact with email: ${email}`) + } + const user = await dbUser.findOne({ id: emailContact.userId }, { withDeleted: true }) if (!user) { - throw new Error(`Could not find user with email: ${email}`) + logger.error(`Could not find User to emailContact: ${email}`) + throw new Error(`Could not find User to emailContact: ${email}`) } if (user.deletedAt) { + logger.error(`User was deleted (${email})`) throw new Error(`User was deleted (${email})`) } @@ -318,14 +336,17 @@ export class AdminResolver { }) if (!contributionToUpdate) { + logger.error('No contribution found to given id.') throw new Error('No contribution found to given id.') } if (contributionToUpdate.userId !== user.id) { + logger.error('user of the pending contribution and send user does not correspond') throw new Error('user of the pending contribution and send user does not correspond') } if (contributionToUpdate.moderatorId === null) { + logger.error('An admin is not allowed to update a user contribution.') throw new Error('An admin is not allowed to update a user contribution.') } @@ -379,7 +400,7 @@ export class AdminResolver { moderator: contribution.moderatorId, firstName: user ? user.firstName : '', lastName: user ? user.lastName : '', - email: user ? user.email : '', + email: user ? user.emailContact.email : '', creation: creation ? creation.creations : FULL_CREATION_AVAILABLE, } }) @@ -390,10 +411,10 @@ export class AdminResolver { async adminDeleteContribution(@Arg('id', () => Int) id: number): Promise { const contribution = await Contribution.findOne(id) if (!contribution) { + logger.error(`Contribution not found for given id: ${id}`) throw new Error('Contribution not found for given id.') } contribution.contributionStatus = ContributionStatus.DELETED - await contribution.save() const res = await contribution.softRemove() return !!res } @@ -406,15 +427,19 @@ export class AdminResolver { ): Promise { const contribution = await Contribution.findOne(id) if (!contribution) { + logger.error(`Contribution not found for given id: ${id}`) throw new Error('Contribution not found to given id.') } const moderatorUser = getUser(context) - if (moderatorUser.id === contribution.userId) + if (moderatorUser.id === contribution.userId) { + logger.error('Moderator can not confirm own contribution') throw new Error('Moderator can not confirm own contribution') - + } const user = await dbUser.findOneOrFail({ id: contribution.userId }, { withDeleted: true }) - if (user.deletedAt) throw new Error('This user was deleted. Cannot confirm a contribution.') - + if (user.deletedAt) { + 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) @@ -501,6 +526,18 @@ export class AdminResolver { @Mutation(() => Boolean) async sendActivationEmail(@Arg('email') email: string): Promise { email = email.trim().toLowerCase() + const emailContact = await UserContact.findOne({ email: email }) + if (!emailContact) { + logger.error(`Could not find UserContact with email: ${email}`) + throw new Error(`Could not find UserContact with email: ${email}`) + } + const user = await dbUser.findOne({ id: emailContact.userId }) + if (!user) { + logger.error(`Could not find User to emailContact: ${email}`) + throw new Error(`Could not find User to emailContact: ${email}`) + } + + /* const user = await dbUser.findOneOrFail({ email: email }) // can be both types: REGISTER and RESET_PASSWORD @@ -510,23 +547,21 @@ export class AdminResolver { }) optInCode = await checkOptInCode(optInCode, user) + */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const emailSent = await sendAccountActivationEmail({ - link: activationLink(optInCode), + link: activationLink(emailContact.emailVerificationCode), firstName: user.firstName, lastName: user.lastName, email, duration: printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME), }) - /* uncomment this, when you need the activation link on the console // In case EMails are disabled log the activation link for the user if (!emailSent) { - // eslint-disable-next-line no-console - console.log(`Account confirmation link: ${activationLink}`) + logger.info(`Account confirmation link: ${activationLink}`) } - */ return true } diff --git a/backend/src/graphql/resolver/GdtResolver.ts b/backend/src/graphql/resolver/GdtResolver.ts index 56a95c9f0..a1d75e946 100644 --- a/backend/src/graphql/resolver/GdtResolver.ts +++ b/backend/src/graphql/resolver/GdtResolver.ts @@ -20,7 +20,7 @@ export class GdtResolver { try { const resultGDT = await apiGet( - `${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.email}/${currentPage}/${pageSize}/${order}`, + `${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.emailContact.email}/${currentPage}/${pageSize}/${order}`, ) if (!resultGDT.success) { throw new Error(resultGDT.data) @@ -37,7 +37,7 @@ export class GdtResolver { const user = getUser(context) try { const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, { - email: user.email, + email: user.emailContact.email, }) if (!resultGDTSum.success) { throw new Error('Call not successful') diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index bc062a1f4..ae6445343 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -35,6 +35,7 @@ import Decimal from 'decimal.js-light' import { BalanceResolver } from './BalanceResolver' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' +import { UserContact } from '@entity/UserContact' export const executeTransaction = async ( amount: Decimal, @@ -148,8 +149,8 @@ export const executeTransaction = async ( senderLastName: sender.lastName, recipientFirstName: recipient.firstName, recipientLastName: recipient.lastName, - email: recipient.email, - senderEmail: sender.email, + email: recipient.emailContact.email, + senderEmail: sender.emailContact.email, amount, memo, overviewURL: CONFIG.EMAIL_LINK_OVERVIEW, @@ -171,7 +172,7 @@ export class TransactionResolver { const user = getUser(context) logger.addContext('user', user.id) - logger.info(`transactionList(user=${user.firstName}.${user.lastName}, ${user.email})`) + logger.info(`transactionList(user=${user.firstName}.${user.lastName}, ${user.emailId})`) // find current balance const lastTransaction = await dbTransaction.findOne( @@ -293,16 +294,22 @@ export class TransactionResolver { } // validate recipient user - const recipientUser = await dbUser.findOne({ email: email }, { withDeleted: true }) + const emailContact = await UserContact.findOne({ email }, { withDeleted: true }) + if (!emailContact) { + logger.error(`Could not find UserContact with email: ${email}`) + throw new Error(`Could not find UserContact with email: ${email}`) + } + + const recipientUser = await dbUser.findOne({ id: emailContact.userId }) if (!recipientUser) { - logger.error(`recipient not known: email=${email}`) - throw new Error('recipient not known') + logger.error(`unknown recipient to UserContact: email=${email}`) + throw new Error('unknown recipient') } if (recipientUser.deletedAt) { logger.error(`The recipient account was deleted: recipientUser=${recipientUser}`) throw new Error('The recipient account was deleted') } - if (!recipientUser.emailChecked) { + if (!emailContact.emailChecked) { logger.error(`The recipient account is not activated: recipientUser=${recipientUser}`) throw new Error('The recipient account is not activated') } diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 9aba4d6b1..37a9946a7 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -29,6 +29,7 @@ import { EventLogin, EventRedeemRegister, EventRegister, + EventSendAccountMultiRegistrationEmail, EventSendConfirmationEmail, } from '@/event/Event' import { getUserCreation } from './util/creations' @@ -417,50 +418,55 @@ export class UserResolver { ) // TODO: wrong default value (should be null), how does graphql work here? Is it an required field? // default int publisher_id = 0; + const event = new Event() // Validate Language (no throw) if (!language || !isLanguage(language)) { language = DEFAULT_LANGUAGE } - // Validate email unique + // check if user with email still exists? email = email.trim().toLowerCase() - const foundUser = await findUserByEmail(email) + if (await checkEmailExists(email)) { + const foundUser = await findUserByEmail(email) + logger.info(`DbUser.findOne(email=${email}) = ${foundUser}`) - // TODO we cannot use repository.count(), since it does not allow to specify if you want to include the soft deletes - // const userFound = await DbUser.findOne({ email }, { withDeleted: true }) - logger.info(`DbUser.findOne(email=${email}) = ${foundUser}`) + if (foundUser) { + // ATTENTION: this logger-message will be exactly expected during tests + logger.info(`User already exists with this email=${email}`) + // TODO: this is unsecure, but the current implementation of the login server. This way it can be queried if the user with given EMail is existent. - if (foundUser) { - // ATTENTION: this logger-message will be exactly expected during tests - logger.info(`User already exists with this email=${email}`) - // TODO: this is unsecure, but the current implementation of the login server. This way it can be queried if the user with given EMail is existent. + const user = new User(communityDbUser) + user.id = sodium.randombytes_random() % (2048 * 16) // TODO: for a better faking derive id from email so that it will be always the same id when the same email comes in? + user.gradidoID = uuidv4() + user.email = email + user.firstName = firstName + user.lastName = lastName + user.language = language + user.publisherId = publisherId + logger.debug('partly faked user=' + user) - const user = new User(communityDbUser) - user.id = sodium.randombytes_random() % (2048 * 16) // TODO: for a better faking derive id from email so that it will be always the same id when the same email comes in? - user.gradidoID = uuidv4() - user.email = email - user.firstName = firstName - user.lastName = lastName - user.language = language - user.publisherId = publisherId - logger.debug('partly faked user=' + user) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const emailSent = await sendAccountMultiRegistrationEmail({ + firstName, + lastName, + email, + }) + const eventSendAccountMultiRegistrationEmail = new EventSendAccountMultiRegistrationEmail() + eventSendAccountMultiRegistrationEmail.userId = foundUser.id + eventProtocol.writeEvent( + event.setEventSendConfirmationEmail(eventSendAccountMultiRegistrationEmail), + ) + logger.info(`sendAccountMultiRegistrationEmail of ${firstName}.${lastName} to ${email}`) + /* uncomment this, when you need the activation link on the console */ + // In case EMails are disabled log the activation link for the user + if (!emailSent) { + logger.debug(`Email not send!`) + } + logger.info('createUser() faked and send multi registration mail...') - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const emailSent = await sendAccountMultiRegistrationEmail({ - firstName, - lastName, - email, - }) - logger.info(`sendAccountMultiRegistrationEmail of ${firstName}.${lastName} to ${email}`) - /* uncomment this, when you need the activation link on the console */ - // In case EMails are disabled log the activation link for the user - if (!emailSent) { - logger.debug(`Email not send!`) + return user } - logger.info('createUser() faked and send multi registration mail...') - - return user } const passphrase = PassphraseGenerate() @@ -473,16 +479,11 @@ export class UserResolver { const eventRegister = new EventRegister() const eventRedeemRegister = new EventRedeemRegister() const eventSendConfirmEmail = new EventSendConfirmationEmail() - // const dbEmailContact = new DbUserContact() - // dbEmailContact.email = email - const dbUser = new DbUser() - // dbUser.emailContact = dbEmailContact + let dbUser = new DbUser() dbUser.gradidoID = gradidoID - // dbUser.email = email dbUser.firstName = firstName dbUser.lastName = lastName - // dbUser.emailHash = emailHash dbUser.language = language dbUser.publisherId = publisherId dbUser.passphrase = passphrase.join(' ') @@ -513,22 +514,22 @@ export class UserResolver { // loginUser.pubKey = keyPair[0] // loginUser.privKey = encryptedPrivkey - const event = new Event() const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('READ UNCOMMITTED') try { - await queryRunner.manager.save(dbUser).catch((error) => { + dbUser = await queryRunner.manager.save(dbUser).catch((error) => { logger.error('Error while saving dbUser', error) throw new Error('error saving user') }) - const emailContact = newEmailContact(email, dbUser.id) - await queryRunner.manager.save(emailContact).catch((error) => { + let emailContact = newEmailContact(email, dbUser.id) + emailContact = await queryRunner.manager.save(emailContact).catch((error) => { logger.error('Error while saving emailContact', error) throw new Error('error saving email user contact') }) dbUser.emailContact = emailContact + dbUser.emailId = emailContact.id await queryRunner.manager.save(dbUser).catch((error) => { logger.error('Error while updating dbUser', error) throw new Error('error updating user') @@ -559,8 +560,6 @@ export class UserResolver { eventSendConfirmEmail.userId = dbUser.id eventProtocol.writeEvent(event.setEventSendConfirmationEmail(eventSendConfirmEmail)) - /* uncomment this, when you need the activation link on the console */ - // In case EMails are disabled log the activation link for the user if (!emailSent) { logger.debug(`Account confirmation link: ${activationLink}`) } @@ -893,20 +892,30 @@ export class UserResolver { } async function findUserByEmail(email: string): Promise { - const dbUserContact = await DbUserContact.findOneOrFail(email, { withDeleted: true }).catch( - () => { - logger.error(`UserContact with email=${email} does not exists`) - throw new Error('No user with this credentials') - }, - ) - const userId = dbUserContact.userId - const dbUser = await DbUser.findOneOrFail(userId).catch(() => { - logger.error(`User with emeilContact=${email} connected per userId=${userId} does not exist`) + const dbUserContact = await DbUserContact.findOneOrFail( + { email: email }, + { withDeleted: true }, + ).catch(() => { + logger.error(`UserContact with email=${email} does not exists`) throw new Error('No user with this credentials') }) + const userId = dbUserContact.userId + const dbUser = await DbUser.findOneOrFail(userId).catch(() => { + logger.error(`User with emailContact=${email} connected per userId=${userId} does not exist`) + throw new Error('No user with this credentials') + }) + dbUser.emailContact = dbUserContact return dbUser } +async function checkEmailExists(email: string): Promise { + const userContact = await DbUserContact.findOne({ email: email }, { withDeleted: true }) + if (userContact) { + return true + } + return false +} + /* const isTimeExpired = (optIn: LoginEmailOptIn, duration: number): boolean => { const timeElapsed = Date.now() - new Date(optIn.updatedAt).getTime() diff --git a/backend/src/typeorm/repository/User.ts b/backend/src/typeorm/repository/User.ts index 01f61dcbc..21cafbb30 100644 --- a/backend/src/typeorm/repository/User.ts +++ b/backend/src/typeorm/repository/User.ts @@ -4,9 +4,15 @@ import { User } from '@entity/User' @EntityRepository(User) export class UserRepository extends Repository { async findByPubkeyHex(pubkeyHex: string): Promise { - return this.createQueryBuilder('user') + const user = await this.createQueryBuilder('user') .where('hex(user.pubKey) = :pubkeyHex', { pubkeyHex }) .getOneOrFail() + /* + user.emailContact = await this.createQueryBuilder('userContact') + .where('userContact.id = :user.emailId', { user.emailId }) + .getOneOrFail() + */ + return user } async findBySearchCriteriaPagedFiltered( diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index 2f0743270..e885b7043 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -12,6 +12,7 @@ const communityDbUser: dbUser = { alias: '', // email: 'support@gradido.net', emailContact: new UserContact(), + emailId: -1, firstName: 'Gradido', lastName: 'Akademie', pubKey: Buffer.from(''), diff --git a/backend/src/webhook/elopage.ts b/backend/src/webhook/elopage.ts index d5eaef521..6c8ca7e49 100644 --- a/backend/src/webhook/elopage.ts +++ b/backend/src/webhook/elopage.ts @@ -30,6 +30,7 @@ import { LoginElopageBuys } from '@entity/LoginElopageBuys' import { UserResolver } from '@/graphql/resolver/UserResolver' import { User as dbUser } from '@entity/User' +import { UserContact as dbUserContact } from '@entity/UserContact' export const elopageWebhook = async (req: any, res: any): Promise => { // eslint-disable-next-line no-console @@ -127,7 +128,8 @@ export const elopageWebhook = async (req: any, res: any): Promise => { } // Do we already have such a user? - if ((await dbUser.count({ email })) !== 0) { + // if ((await dbUser.count({ email })) !== 0) { + if ((await dbUserContact.count({ email })) !== 0) { // eslint-disable-next-line no-console console.log(`Did not create User - already exists with email: ${email}`) return diff --git a/database/entity/0048-add_user_contacts_table/User.ts b/database/entity/0048-add_user_contacts_table/User.ts index 010cb0c20..40bfa601a 100644 --- a/database/entity/0048-add_user_contacts_table/User.ts +++ b/database/entity/0048-add_user_contacts_table/User.ts @@ -43,12 +43,12 @@ export class User extends BaseEntity { @Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' }) email: string */ - @OneToOne(() => UserContact, { primary: true, cascade: true }) + @OneToOne(() => UserContact) @JoinColumn({ name: 'email_id' }) emailContact: UserContact @Column({ name: 'email_id', type: 'int', unsigned: true, nullable: true, default: null }) - emailId?: number | null + emailId: number | null @Column({ name: 'first_name', diff --git a/database/entity/0048-add_user_contacts_table/UserContact.ts b/database/entity/0048-add_user_contacts_table/UserContact.ts index 7c2dff3db..936e433a6 100644 --- a/database/entity/0048-add_user_contacts_table/UserContact.ts +++ b/database/entity/0048-add_user_contacts_table/UserContact.ts @@ -1,4 +1,13 @@ -import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, DeleteDateColumn } from 'typeorm' +import { + BaseEntity, + Entity, + PrimaryGeneratedColumn, + Column, + DeleteDateColumn, + OneToOne, + JoinColumn, +} from 'typeorm' +import { User } from './User' @Entity('user_contacts', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) export class UserContact extends BaseEntity { @@ -14,6 +23,10 @@ export class UserContact extends BaseEntity { }) type: string + @OneToOne(() => User) + @JoinColumn({ name: 'user_id' }) + user: User + @Column({ name: 'user_id', type: 'int', unsigned: true, nullable: false }) userId: number diff --git a/database/migrations/0048-add_user_contacts_table.ts b/database/migrations/0048-add_user_contacts_table.ts index 49f647e39..d1b35a400 100644 --- a/database/migrations/0048-add_user_contacts_table.ts +++ b/database/migrations/0048-add_user_contacts_table.ts @@ -22,7 +22,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis \`email_checked\` tinyint(4) NOT NULL DEFAULT 0, \`phone\` varchar(255) COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, \`created_at\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - \`updated_at\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + \`updated_at\` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, \`deleted_at\` datetime NULL DEFAULT NULL, PRIMARY KEY (\`id\`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)