From a9e6dc58341991a4ad3f87e0a672615a979274fb Mon Sep 17 00:00:00 2001 From: Alessandro Magionami Date: Mon, 8 Jul 2024 17:27:59 +0200 Subject: [PATCH] feat(user-emails): create user emails table and type (#2470) * feat(user-emails): create user emails table and type * chore(user-emails): add delete cascade * chore(user-emails): fix tests * chore(user-emails): add unique constraint for email and userId * chore(user-emails): use random email in tests * chore(user-emails): add todo to remove user-email in test * chore(user-emails): code review changes * chore(user-emails): use random passwords for tests * chore(user-emails): fix test and code review changes --- .../tests/integration/user-emails.spec.ts | 51 +++++++++++++++++++ .../emails/tests/verifications.spec.ts | 3 +- .../server/modules/user-emails/constants.ts | 1 + .../modules/user-emails/domain/types.ts | 9 ++++ .../migrations/20240703084247_user-emails.ts | 29 +++++++++++ 5 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 packages/server/modules/core/tests/integration/user-emails.spec.ts create mode 100644 packages/server/modules/user-emails/constants.ts create mode 100644 packages/server/modules/user-emails/domain/types.ts create mode 100644 packages/server/modules/user-emails/migrations/20240703084247_user-emails.ts diff --git a/packages/server/modules/core/tests/integration/user-emails.spec.ts b/packages/server/modules/core/tests/integration/user-emails.spec.ts new file mode 100644 index 0000000000..056be96a99 --- /dev/null +++ b/packages/server/modules/core/tests/integration/user-emails.spec.ts @@ -0,0 +1,51 @@ +import { before } from 'mocha' +import { createUser } from '@/modules/core/services/users' +import { beforeEachContext } from '@/test/hooks' +import { expect } from 'chai' +import { getUserByEmail } from '@/modules/core/repositories/users' +import crs from 'crypto-random-string' + +function createRandomEmail() { + return `${crs({ length: 6 })}@example.org` +} +function createRandomPassword() { + return crs({ length: 10 }) +} + +describe('Core @user-emails', () => { + before(async () => { + await beforeEachContext() + }) + describe('getUserByEmail', () => { + it('should return null if user does not exist', async () => { + expect(await getUserByEmail('test@example.org')).to.be.null + }) + + it('should return user if user-email does not exist', async () => { + const email = createRandomEmail() + await createUser({ + name: 'John Doe', + email, + password: createRandomPassword() + }) + // TODO: delete user email + + const user = (await getUserByEmail(email))! + expect(user.name).to.eq('John Doe') + expect(user.email).to.eq(email) + }) + + it('should return user merged with user-email', async () => { + const email = createRandomEmail() + await createUser({ + name: 'John Doe', + email, + password: createRandomPassword() + }) + + const user = (await getUserByEmail(email))! + expect(user.name).to.eq('John Doe') + expect(user.email).to.eq(email) + }) + }) +}) diff --git a/packages/server/modules/emails/tests/verifications.spec.ts b/packages/server/modules/emails/tests/verifications.spec.ts index 0b6812a99e..87abb34d94 100644 --- a/packages/server/modules/emails/tests/verifications.spec.ts +++ b/packages/server/modules/emails/tests/verifications.spec.ts @@ -19,11 +19,12 @@ import { Express } from 'express' import { getUser } from '@/modules/core/repositories/users' import dayjs from 'dayjs' import { EmailSendingServiceMock } from '@/test/mocks/global' +import { USER_EMAILS_TABLE_NAME } from '@/modules/user-emails/constants' const mailerMock = EmailSendingServiceMock const cleanup = async () => { - await truncateTables([Users.name, EmailVerifications.name]) + await truncateTables([Users.name, EmailVerifications.name, USER_EMAILS_TABLE_NAME]) } describe('Email verifications @emails', () => { diff --git a/packages/server/modules/user-emails/constants.ts b/packages/server/modules/user-emails/constants.ts new file mode 100644 index 0000000000..966330e7e7 --- /dev/null +++ b/packages/server/modules/user-emails/constants.ts @@ -0,0 +1 @@ +export const USER_EMAILS_TABLE_NAME = 'user_emails' diff --git a/packages/server/modules/user-emails/domain/types.ts b/packages/server/modules/user-emails/domain/types.ts new file mode 100644 index 0000000000..18b33c784c --- /dev/null +++ b/packages/server/modules/user-emails/domain/types.ts @@ -0,0 +1,9 @@ +export type UserEmail = { + id: string + email: string + primary: boolean + verified: boolean + userId: string + createdAt: Date + updatedAt: Date +} diff --git a/packages/server/modules/user-emails/migrations/20240703084247_user-emails.ts b/packages/server/modules/user-emails/migrations/20240703084247_user-emails.ts new file mode 100644 index 0000000000..8330cbee94 --- /dev/null +++ b/packages/server/modules/user-emails/migrations/20240703084247_user-emails.ts @@ -0,0 +1,29 @@ +import { Knex } from 'knex' + +export async function up(knex: Knex): Promise { + await knex.schema.createTable('user_emails', (table) => { + table.string('id').notNullable().primary() + table.string('email').notNullable() + table.boolean('primary').defaultTo(false) + table.boolean('verified').defaultTo(false) + table + .string('userId') + .notNullable() + .references('id') + .inTable('users') + .onDelete('cascade') + table + .timestamp('createdAt', { precision: 3, useTz: true }) + .defaultTo(knex.fn.now()) + .notNullable() + table + .timestamp('updatedAt', { precision: 3, useTz: true }) + .defaultTo(knex.fn.now()) + .notNullable() + table.unique(['userId', 'email']) + }) +} + +export async function down(knex: Knex): Promise { + await knex.schema.dropTableIfExists('user_emails') +}