diff --git a/apps/api/src/mail/services/interface.service.ts b/apps/api/src/mail/services/interface.service.ts index 31ac434c..bb8f2c0b 100644 --- a/apps/api/src/mail/services/interface.service.ts +++ b/apps/api/src/mail/services/interface.service.ts @@ -20,4 +20,8 @@ export interface IMailService { invitedBy: string, role: WorkspaceRole ): Promise + + accountLoginEmail( + email: string + ): Promise } diff --git a/apps/api/src/mail/services/mail.service.ts b/apps/api/src/mail/services/mail.service.ts index f909339d..42151f1f 100644 --- a/apps/api/src/mail/services/mail.service.ts +++ b/apps/api/src/mail/services/mail.service.ts @@ -98,6 +98,28 @@ export class MailService implements IMailService { await this.sendEmail(email, subject, body) } + async accountLoginEmail( + email: string + ): Promise { + const subject = 'LogIn Invitation Accepted' + const body = ` + + + LogIn Invitaion + + +

Welcome to keyshade!

+

Hello there!

+

Your account has been setup. Please login to your account for further process.

+

Thank you for choosing us.

+

Best Regards,

+

keyshade Team

+ + + ` + await this.sendEmail(email, subject, body) + } + private async sendEmail( email: string, subject: string, diff --git a/apps/api/src/mail/services/mock.service.ts b/apps/api/src/mail/services/mock.service.ts index a51498f6..d94562b0 100644 --- a/apps/api/src/mail/services/mock.service.ts +++ b/apps/api/src/mail/services/mock.service.ts @@ -33,4 +33,8 @@ export class MockMailService implements IMailService { async sendOtp(email: string, otp: string): Promise { this.log.log(`OTP for ${email} is ${otp}`) } + + async accountLoginEmail(email: string): Promise { + this.log.log(`Account Login Email for ${email}`) + } } diff --git a/apps/api/src/user/controller/user.controller.spec.ts b/apps/api/src/user/controller/user.controller.spec.ts index 1ecb5dbb..c6303854 100644 --- a/apps/api/src/user/controller/user.controller.spec.ts +++ b/apps/api/src/user/controller/user.controller.spec.ts @@ -4,6 +4,8 @@ import { UserService } from '../service/user.service' import { User } from '@prisma/client' import { PrismaService } from '../../prisma/prisma.service' import { mockDeep } from 'jest-mock-extended' +import { MAIL_SERVICE } from '../../mail/services/interface.service' +import { MockMailService } from '../../mail/services/mock.service' describe('UserController', () => { let controller: UserController @@ -22,7 +24,11 @@ describe('UserController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [UserController], - providers: [UserService, PrismaService] + providers: [ + UserService, + PrismaService, + { provide: MAIL_SERVICE, useValue: MockMailService } + ] }) .overrideProvider(PrismaService) .useValue(mockDeep()) diff --git a/apps/api/src/user/controller/user.controller.ts b/apps/api/src/user/controller/user.controller.ts index f7a206bb..7c34fb30 100644 --- a/apps/api/src/user/controller/user.controller.ts +++ b/apps/api/src/user/controller/user.controller.ts @@ -3,6 +3,7 @@ import { Controller, Get, Param, + Post, Put, Query, UseGuards @@ -12,6 +13,7 @@ import { CurrentUser } from '../../decorators/user.decorator' import { User } from '@prisma/client' import { UpdateUserDto } from '../dto/update.user/update.user' import { AdminGuard } from '../../auth/guard/admin.guard' +import { CreateUserDto } from '../dto/create.user/create.user' import { ApiTags } from '@nestjs/swagger' @ApiTags('User Controller') @@ -60,4 +62,10 @@ export class UserController { ) { return await this.userService.getAllUsers(page, limit, sort, order, search) } + + @Post('') + @UseGuards(AdminGuard) + async createUser(@Body() dto: CreateUserDto) { + return await this.userService.createUser(dto); + } } diff --git a/apps/api/src/user/dto/create.user/create.user.ts b/apps/api/src/user/dto/create.user/create.user.ts new file mode 100644 index 00000000..31924807 --- /dev/null +++ b/apps/api/src/user/dto/create.user/create.user.ts @@ -0,0 +1,18 @@ +import { IsBoolean, IsOptional, IsString } from "class-validator"; + +export class CreateUserDto { + @IsString() + @IsOptional() + name: string; + @IsString() + email: string; + @IsString() + @IsOptional() + profilePictureUrl: string; + @IsBoolean() + isActive: boolean; + @IsBoolean() + isOnboardingFinished: boolean; + @IsBoolean() + isAdmin: boolean; +} \ No newline at end of file diff --git a/apps/api/src/user/service/user.service.spec.ts b/apps/api/src/user/service/user.service.spec.ts index c7177914..0f499de1 100644 --- a/apps/api/src/user/service/user.service.spec.ts +++ b/apps/api/src/user/service/user.service.spec.ts @@ -3,6 +3,8 @@ import { UserService } from './user.service' import { User } from '@prisma/client' import { PrismaService } from '../../prisma/prisma.service' import { mockDeep } from 'jest-mock-extended' +import { MAIL_SERVICE } from '../../mail/services/interface.service' +import { MockMailService } from '../../mail/services/mock.service' describe('UserService', () => { let service: UserService @@ -19,7 +21,11 @@ describe('UserService', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [UserService, PrismaService] + providers: [ + UserService, + PrismaService, + { provide: MAIL_SERVICE, useValue: MockMailService } + ] }) .overrideProvider(PrismaService) .useValue(mockDeep()) diff --git a/apps/api/src/user/service/user.service.ts b/apps/api/src/user/service/user.service.ts index 150701e1..38b1ecfb 100644 --- a/apps/api/src/user/service/user.service.ts +++ b/apps/api/src/user/service/user.service.ts @@ -1,8 +1,13 @@ -import { Injectable, Logger } from '@nestjs/common' +import { ConflictException, Inject, Injectable, Logger } from '@nestjs/common' import { UpdateUserDto } from '../dto/update.user/update.user' import { User } from '@prisma/client' import { excludeFields } from '../../common/exclude-fields' import { PrismaService } from '../../prisma/prisma.service' +import { CreateUserDto } from '../dto/create.user/create.user' +import { + IMailService, + MAIL_SERVICE +} from '../../mail/services/interface.service' @Injectable() export class UserService { @@ -10,7 +15,8 @@ export class UserService { constructor( // @Inject(USER_REPOSITORY) private readonly repository: IUserRepository - private readonly prisma: PrismaService + private readonly prisma: PrismaService, + @Inject(MAIL_SERVICE) private readonly mailService: IMailService ) {} async getSelf(user: User) { @@ -115,4 +121,52 @@ export class UserService { } }) } + + async createUser(user: CreateUserDto) { + this.log.log(`Creating user with email ${user.email}`) + + // Check for duplicate user + const checkDuplicateUser = + (await this.prisma.user.count({ + where: { + email: user.email + } + })) > 0 + if (checkDuplicateUser) { + throw new ConflictException('User already exists with this email') + } + + // Create the user + const newUser = await this.prisma.user.create({ + data: { + name: user.name, + email: user.email, + profilePictureUrl: user.profilePictureUrl, + isActive: user.isActive ?? true, + isOnboardingFinished: user.isOnboardingFinished ?? true, + isAdmin: user.isAdmin ?? false + } + }) + this.log.log(`Created user with email ${user.email}`) + + // Create the user's default workspace + await this.prisma.workspace.create({ + data: { + name: 'Default', + isDefault: true, + ownerId: newUser.id, + lastUpdatedBy: { + connect: { + id: newUser.id + } + } + } + }) + this.log.log(`Created user's default workspace`) + + await this.mailService.accountLoginEmail(newUser.email) + this.log.log(`Sent login email to ${user.email}`) + + return newUser + } }