Skip to content
This repository has been archived by the owner on Dec 16, 2024. It is now read-only.

Commit

Permalink
feat: implements email service and sign-up service
Browse files Browse the repository at this point in the history
  • Loading branch information
jspark2000 committed Mar 22, 2024
1 parent 9689200 commit d3d5b75
Show file tree
Hide file tree
Showing 16 changed files with 1,716 additions and 146 deletions.
3 changes: 2 additions & 1 deletion aws/royals/ecs-api.tf
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ resource "aws_ecs_task_definition" "api" {
redis_port = var.redis_port,
jwt_secret = var.jwt_secret,
cdn_base_url = var.cdn_base_url,
aws_cdn_bucket_name = var.aws_cdn_bucket_name
aws_cdn_bucket_name = var.aws_cdn_bucket_name,
nodemailer_from = "SKKU ROYALS <[email protected]>"
})

execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
Expand Down
4 changes: 4 additions & 0 deletions aws/royals/task-definition.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
{
"name": "CDN_BASE_URL",
"value": "${cdn_base_url}"
},
{
"name": "NODEMAILER_FROM",
"value": "${nodemailer_from}"
}
],
"logConfiguration": {
Expand Down
9 changes: 8 additions & 1 deletion backend/app/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MailerModule } from '@nestjs-modules/mailer'
import { CacheModule } from '@nestjs/cache-manager'
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
Expand All @@ -16,6 +17,8 @@ import { AppController } from './app.controller'
import { AppService } from './app.service'
import { AttendanceModule } from './attendance/attendance.module'
import { AuthModule } from './auth/auth.module'
import { EmailModule } from './email/email.module'
import { MailerConfigService } from './email/mailer-config.service'
import { RosterModule } from './roster/roster.module'
import { SurveyModule } from './survey/survey.module'
import { UserModule } from './user/user.module'
Expand All @@ -27,6 +30,9 @@ import { UserModule } from './user/user.module'
isGlobal: true,
useClass: CacheConfigService
}),
MailerModule.forRootAsync({
useClass: MailerConfigService
}),
AuthModule,
PrismaModule,
JwtAuthModule,
Expand All @@ -35,7 +41,8 @@ import { UserModule } from './user/user.module'
StorageModule,
RosterModule,
SurveyModule,
AttendanceModule
AttendanceModule,
EmailModule
],
controllers: [AppController],
providers: [
Expand Down
8 changes: 8 additions & 0 deletions backend/app/src/email/email.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common'
import { EmailService } from './email.service'

@Module({
providers: [EmailService],
exports: [EmailService]
})
export class EmailModule {}
16 changes: 16 additions & 0 deletions backend/app/src/email/email.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MailerService } from '@nestjs-modules/mailer'
import { Service } from '@libs/decorator'

@Service()
export class EmailService {
constructor(private readonly mailerService: MailerService) {}

async sendEmailAuthenticationPin(email: string, pin: string): Promise<void> {
await this.mailerService.sendMail({
to: email,
subject: '[SKKU ROYALS] 이메일 인증',
template: 'email-auth',
context: { pin }
})
}
}
48 changes: 48 additions & 0 deletions backend/app/src/email/mailer-config.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type {
MailerOptions,
MailerOptionsFactory
} from '@nestjs-modules/mailer'
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { SESClient } from '@aws-sdk/client-ses'
import { defaultProvider } from '@aws-sdk/credential-provider-node'
import { calculateSesSmtpPassword } from '@pepperize/cdk-ses-smtp-credentials'
import { join } from 'path'

@Injectable()
export class MailerConfigService implements MailerOptionsFactory {
constructor(private readonly config: ConfigService) {}

createMailerOptions(): MailerOptions {
return {
transport: this.config.get('NODEMAILER_HOST')
? {
host: this.config.get('NODEMAILER_HOST'),
auth: {
user: this.config.get('NODEMAILER_USER'),
pass: calculateSesSmtpPassword(
this.config.get('NODEMAILER_PASS'),
'ap-northeast-2'
)
}
}
: {
SES: new SESClient({
region: 'ap-northeast-2',
credentials: defaultProvider()
})
},
defaults: {
from: this.config.get('NODEMAILER_FROM')
},
template: {
dir: join(__dirname, 'email/templates'),
adapter: new HandlebarsAdapter(),
options: {
strict: true
}
}
}
}
}
43 changes: 43 additions & 0 deletions backend/app/src/email/templates/email-auth.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<style>
* { margin: 0; padding: 0; font-family: 'Manrope', Helvetica, Arial,
sans-serif; } body { background: #ececec; } p { margin: 16px 0; font-size:
16px; line-height: 24px; } h1 { margin-top: 32px; margin-bottom: 16px;
font-size: 24px; } #logo { display: block; margin-left: auto; margin-right:
auto; } #backgroundTable { margin-left: auto; margin-right: auto; } #mainTable
{ max-width: 420px; background: white; margin: 20px; padding: 24px;
border-radius: 12px; box-shadow: 4px 4px 12px 4px rgba(0, 0, 0, 0.15); }
#backgroundTable { padding: 20px; } #pin { font-size: 36px; text-align:
center; margin: 60px 0; letter-spacing: 12px; } #team { margin-top: 40px;
text-align: right; } footer { border-top: 1px solid #777; color: #777;
margin-top: 40px; padding-top: 20px; font-size: 14px; }
</style>

<table id='backgroundTable' cellpadding='0' cellspacing='0' border='0'>
<tr>
<td>
<table id='mainTable' cellpadding='0' cellspacing='0' align='center'>
<tr>
<td>
<img
src='https://github.com/skku-royals/official-webpage/blob/main/frontend/public/text-logo-light.png?raw=true'
height='72'
alt='Royals'
id='logo'
/>
<h1>Email Authentication</h1>
<p>You have requested an email authentication. Put the pin numbers
below into the form.</p>
<p id='pin'>{{pin}}</p>
<p>If you did not request to register or change your information,
please ignore this email.</p>
<p id='team'>성균관대학교 미식축구부 로얄스</p>
<footer>
Visit our website at
<a href='https://skku-royals.com'>skku-royals.com</a>.
</footer>
</td>
</tr>
</table>
</td>
</tr>
</table>
59 changes: 59 additions & 0 deletions backend/app/src/user/dto/user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AccountStatus, Role } from '@prisma/client'
import { Exclude, Expose } from 'class-transformer'
import {
IsEmail,
IsEnum,
Expand Down Expand Up @@ -26,3 +27,61 @@ export class UpdateUserDTO {
@IsOptional()
status?: AccountStatus
}

export class CreateUserDTO {
@IsString()
@IsNotEmpty()
username: string

@IsString()
@IsNotEmpty()
password: string

@IsEmail()
@IsNotEmpty()
email: string

@IsString()
@IsNotEmpty()
nickname: string
}

export class ReducedUser {
@Expose()
id: number

@Expose()
username: string

@Expose()
email: string

@Expose()
nickname: string

@Expose()
role: Role

@Expose()
status: AccountStatus

@Expose()
lastLogin: string

@Expose()
profileImageUrl?: string

@Exclude()
password: string
}

export interface ReducedUserDTO {
id: number
username: string
email: string
nickname: string
role: Role
status: AccountStatus
lastLogin: string
profileImageUrl?: string
}
37 changes: 33 additions & 4 deletions backend/app/src/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Get,
Param,
ParseIntPipe,
Post,
Put,
Query,
Req,
Expand All @@ -13,11 +14,16 @@ import {
} from '@nestjs/common'
import { FileInterceptor } from '@nestjs/platform-express'
import { AuthenticatedRequest } from '@libs/auth'
import { Roles } from '@libs/decorator'
import { Public, Roles } from '@libs/decorator'
import { BusinessExceptionHandler } from '@libs/exception'
import { IMAGE_OPTIONS } from '@libs/storage'
import { Role, type User } from '@prisma/client'
import { UpdateUserProfileDTO, type UpdateUserDTO } from './dto/user.dto'
import { Role } from '@prisma/client'
import {
UpdateUserProfileDTO,
type UpdateUserDTO,
type ReducedUserDTO,
type CreateUserDTO
} from './dto/user.dto'
import { UserService } from './user.service'

@Controller('user')
Expand Down Expand Up @@ -58,12 +64,35 @@ export class UserController {
}
}

@Public()
@Post()
async signUp(@Body() userDTO: CreateUserDTO): Promise<ReducedUserDTO> {
try {
return await this.userService.signUp(userDTO)
} catch (error) {
BusinessExceptionHandler(error)
}
}

@Public()
@Post('verify-email')
async verifyEmailAddress(
@Query('email') email: string,
@Query('pin') pin: string
): Promise<{ valid: boolean }> {
try {
return await this.userService.verifyEmailPin(email, pin)
} catch (error) {
BusinessExceptionHandler(error)
}
}

@Roles(Role.Admin)
@Put(':userId')
async updateUser(
@Param('userId', ParseIntPipe) userId: number,
@Body() userDTO: UpdateUserDTO
): Promise<User> {
): Promise<ReducedUserDTO> {
try {
return await this.userService.updateUser(userId, userDTO)
} catch (error) {
Expand Down
2 changes: 2 additions & 0 deletions backend/app/src/user/user.module.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Module } from '@nestjs/common'
import { EmailModule } from '@/email/email.module'
import { UserController } from './user.controller'
import { UserService } from './user.service'

@Module({
imports: [EmailModule],
controllers: [UserController],
providers: [UserService],
exports: [UserService]
Expand Down
Loading

0 comments on commit d3d5b75

Please sign in to comment.