Skip to content

Commit

Permalink
feat: added email module and upcoming event email, close #255
Browse files Browse the repository at this point in the history
  • Loading branch information
cuma.duran committed Jul 3, 2023
1 parent ddd7f77 commit 318ec8c
Show file tree
Hide file tree
Showing 14 changed files with 2,906 additions and 937 deletions.
18 changes: 10 additions & 8 deletions server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=

COOKIE_DOMAIN=

REDIRECT_URL=

JWT_SECRET=
CLIENT_URL=
ALLOWED_ORIGINS=

JUDGE0_KEY=
JUDGE0_URL=

JWKS_URI=
JWKS_ISSUER=
JWKS_COOKIE_NAME=
JWKS_HEADER_NAME=
JWKS_ALGORITHM=
MAIL_IS_ENABLED=
MAIL_ADDRESS=
MAIL_HOST=
MAIL_PORT=
MAIL_SECURE=
MAIL_USER=
MAIL_PASSWORD=

SOCIAL_DISCORD=
SOCIAL_EMAIL=
7 changes: 6 additions & 1 deletion server/nest-cli.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
"sourceRoot": "src",
"compilerOptions": {
"assets": [
{ "include": "**/*.hbs","watchAssets": true }
]
}
}
4 changes: 4 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs-modules/mailer": "^1.8.1",
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
Expand All @@ -38,9 +39,11 @@
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"express": "^4.18.2",
"handlebars": "^4.7.7",
"helmet": "^6.0.1",
"jwks-rsa": "^3.0.1",
"luxon": "^3.2.1",
"nodemailer": "^6.9.3",
"ottoman": "^2.3.0",
"passport": "^0.6.0",
"passport-google-oauth2": "^0.2.0",
Expand All @@ -66,6 +69,7 @@
"@types/luxon": "^3.2.0",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/nodemailer": "^6.4.8",
"@types/passport-google-oauth2": "^0.1.5",
"@types/passport-jwt": "^3.0.8",
"@types/passport-local": "^1.0.35",
Expand Down
26 changes: 26 additions & 0 deletions server/src/core/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ export interface Config {
path: string;
};
};
mail: {
isEnabled: string;
address: string;
host: string;
port: string;
secure: string;
user: string;
password: string;
};
social: {
discord: string;
email: string;
};
}

export const config = {
Expand Down Expand Up @@ -93,6 +106,19 @@ export const config = {
path: process.env.STORAGE_CDN_PATH,
},
},
mail:{
isEnabled: process.env.MAIL_IS_ENABLED,
address: process.env.MAIL_ADDRESS,
host: process.env.MAIL_HOST,
port: process.env.MAIL_PORT,
secure: process.env.MAIL_SECURE,
user: process.env.MAIL_USER,
password:process.env.MAIL_PASSWORD,
},
social:{
discord: process.env.SOCIAL_DISCORD,
email: process.env.SOCIAL_EMAIL,
}
};

export default config as Config;
19 changes: 19 additions & 0 deletions server/src/core/data/services/mail.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {UserEntity} from "@core/data/entities";

import {MAIL_TEMPLATES} from "../../../modules/providers/mail/models/enums";

export type Attachment = {
filename: string;
path: string;
cid: string;
}
export type CodexMailOptions = {
from?: string;
subject: string;
template: MAIL_TEMPLATES;
context?: object;
attachments?: Attachment[];
}
export abstract class IMailService<T> {
abstract send: (user: UserEntity, options?: CodexMailOptions) => Promise<T>;
}
4 changes: 3 additions & 1 deletion server/src/modules/challenge/challenge.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { RoomModule } from '../room/room.module';
import { TeamModule } from '../team/team.module';
import { ChallengeController } from './challenge.controller';
import { ChallengeService } from './challenge.service';
import { MailModule } from '../providers/mail/mail.module';


@Module({
imports: [DataModule, TeamModule, LobbyModule, RoomModule, ScheduleModule.forRoot()],
imports: [DataModule, TeamModule, LobbyModule, RoomModule, ScheduleModule.forRoot(), MailModule],
providers: [ChallengeService],
controllers: [ChallengeController],
exports: [ChallengeService],
Expand Down
29 changes: 26 additions & 3 deletions server/src/modules/challenge/challenge.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import config from '@core/config/configuration';
import { IDataService } from '@core/data/services/data.service';
import {CodexMailOptions} from "@core/data/services/mail.service";
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { Interval } from '@nestjs/schedule';
import { DateTime } from 'luxon';

import { LobbyService } from '../lobby/lobby.service';
import { MailService } from '../providers/mail/mail.service';
import { MAIL_TEMPLATES } from "../providers/mail/models/enums";
import { SubmissionStatus } from '../submission/models/enums';
import { TeamService } from '../team/team.service';
import { CreateChallengeDto } from './dtos/create-challenge.dto';
Expand All @@ -15,14 +19,15 @@ import {
POINTS_GAP,
STATUS_INTERVAL,
} from './models/constants';
import { Status } from './models/enums';
import {Status} from './models/enums';

@Injectable()
export class ChallengeService {
constructor(
private readonly dataService: IDataService,
private readonly teamService: TeamService,
private readonly lobbyService: LobbyService,
private readonly mailService: MailService,
) {}

async create(createChallengeDto: CreateChallengeDto) {
Expand Down Expand Up @@ -155,16 +160,34 @@ export class ChallengeService {
status: { $lt: Status.finished },
});

unfinishedChallenges.forEach(async ({ id, status, date, duration, participants }) => {
for (const {id, status, date, duration, participants, name} of unfinishedChallenges) {
const currentStatus = this.getCurrentStatus(date, duration);

if (currentStatus != status) {
await this.dataService.challenges.update(id, { status: currentStatus });
Logger.log(`Challenge status updated ${id}: ${Status[currentStatus]}`);
if (currentStatus == Status.ongoing) this.startChallenge(id);
if (currentStatus == Status.pending) this.sendUpcomingMailToUser(participants, name, id);
if (currentStatus == Status.finished) this.finishChallenge(id);
}
});
}
}

async sendUpcomingMailToUser(participants: string[], name: string, id: string){
for (const userId of participants) {
const user = await this.dataService.users.findById(userId);

const options: CodexMailOptions = {
subject: `Reminder from Codex | ${name} will start soon!`,
template: MAIL_TEMPLATES.upcoming,
context: {
url: `${config.clientUrl}/challenge/${id}`,
challengeName: name
}
}

await this.mailService.send(user, options);
}
}

getCurrentStatus(date: Date, duration: number) {
Expand Down
2 changes: 1 addition & 1 deletion server/src/modules/challenge/models/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export enum Status {
pending = 2,
ongoing = 3,
finished = 4,
}
}
33 changes: 33 additions & 0 deletions server/src/modules/providers/mail/mail.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import config from '@core/config/configuration';
import { Module } from '@nestjs/common';
import { MailerModule } from '@nestjs-modules/mailer';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
import { join } from 'path';

import { MailService } from './mail.service';

@Module({
imports: [
MailerModule.forRoot({
transport: {
host: config.mail.host,
port: Number(config.mail.port) || 465,
secure: Boolean(config.mail.secure) || true,
auth: {
user: config.mail.user,
pass: config.mail.password,
},
},
template: {
dir: join(__dirname, 'templates'),
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
}),
],
providers: [MailService],
exports: [MailService],
})
export class MailModule {}
34 changes: 34 additions & 0 deletions server/src/modules/providers/mail/mail.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import config from '@core/config/configuration';
import { UserEntity } from '@core/data/entities';
import {CodexMailOptions, IMailService} from "@core/data/services/mail.service";
import { Injectable } from '@nestjs/common';
import { MailerService } from '@nestjs-modules/mailer';

import { MAIL_DEFAULT_FROM, MAIL_DEFAULT_SUBJECT } from "./models/constants";

@Injectable()
export class MailService implements IMailService<any> {
constructor(private mailerService: MailerService) {}

async send(user: UserEntity, options: CodexMailOptions) {
try {
if(Boolean(config.mail.isEnabled)){
await this.mailerService.sendMail({
to: user.email,
from: options.from || MAIL_DEFAULT_FROM,
subject: options.subject || MAIL_DEFAULT_SUBJECT,
template: options.template,
attachments:options.attachments ?? [],
context: {
name: user.name,
discord: config.social.discord,
email: config.social.email,
...options?.context
},
});
}
} catch (e){
console.warn("Mail could not send: ", e);
}
}
}
5 changes: 5 additions & 0 deletions server/src/modules/providers/mail/models/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import config from '@core/config/configuration';
export const MAIL_DEFAULT_FROM = ` "Codex Team" <${config.mail.address}>`;
export const MAIL_DEFAULT_SUBJECT = "Noreply | Codex Team";


3 changes: 3 additions & 0 deletions server/src/modules/providers/mail/models/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum MAIL_TEMPLATES {
upcoming = "./upcoming-event",
}
Loading

0 comments on commit 318ec8c

Please sign in to comment.