Skip to content

Commit 6c35dd4

Browse files
authored
multiple tasks (#246)
* Fix: email failure breaks app * Add email model to store sent emails and check if interns got them * change interview email * Update email actions * fix and add logs * Clean up and add emial check on more emails * Add updated and created to email table; add track to all emails * Fix showing invisible image * fix * Increase interview slot to 20 mins (#245) * Remove extra env variable * return to closed applications * Refactor
1 parent 55149f9 commit 6c35dd4

File tree

17 files changed

+202
-97
lines changed

17 files changed

+202
-97
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- CreateTable
2+
CREATE TABLE "Email" (
3+
"id" TEXT NOT NULL,
4+
"subject" JSONB NOT NULL,
5+
"body" JSONB NOT NULL,
6+
"isSeen" BOOLEAN NOT NULL,
7+
"internId" TEXT NOT NULL,
8+
9+
CONSTRAINT "Email_pkey" PRIMARY KEY ("id")
10+
);
11+
12+
-- AddForeignKey
13+
ALTER TABLE "Email" ADD CONSTRAINT "Email_internId_fkey" FOREIGN KEY ("internId") REFERENCES "Intern"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "Email" ALTER COLUMN "isSeen" SET DEFAULT false;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
Warnings:
3+
4+
- Added the required column `lastUpdatedAt` to the `Email` table without a default value. This is not possible if the table is not empty.
5+
6+
*/
7+
-- AlterTable
8+
ALTER TABLE "Email" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
9+
ADD COLUMN "lastUpdatedAt" TIMESTAMP(3) NOT NULL;

apps/api/prisma/schema.prisma

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,14 @@ model InterviewQuestion {
156156
}
157157

158158
model Email {
159-
id String @id @default(uuid())
159+
id String @id @default(uuid())
160160
subject Json
161161
body Json
162-
isSeen Boolean @default(false)
162+
isSeen Boolean @default(false)
163163
internId String
164-
emailRecipient Intern @relation(fields: [internId], references: [id])
164+
emailRecipient Intern @relation(fields: [internId], references: [id])
165+
createdAt DateTime @default(now())
166+
lastUpdatedAt DateTime @updatedAt
165167
}
166168

167169
enum QuestionCategory {

apps/api/prisma/seed.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -278,15 +278,6 @@ async function main() {
278278
},
279279
});
280280

281-
await prisma.admin.createMany({
282-
data: [
283-
{
284-
email: 'admin@dump.hr',
285-
password: await bcrypt.hash('dump.1950', 10),
286-
},
287-
],
288-
});
289-
290281
await prisma.interviewQuestion.createMany({
291282
data: [
292283
{

apps/api/src/auth/auth.controller.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ import { JwtAuthGuard } from './jwt-auth-guard';
77
export class AuthController {
88
constructor(private readonly authService: AuthService) {}
99

10-
@Post('login')
11-
async login(@Body() { email, password }) {
12-
const accessToken = await this.authService.adminPasswordLogin(
13-
email,
14-
password,
15-
);
10+
// @Post('login')
11+
// async login(@Body() { email, password }) {
12+
// const accessToken = await this.authService.adminPasswordLogin(
13+
// email,
14+
// password,
15+
// );
1616

17-
return {
18-
access_token: accessToken,
19-
};
20-
}
17+
// return {
18+
// access_token: accessToken,
19+
// };
20+
// }
2121

2222
@UseGuards(JwtAuthGuard)
2323
@Get('whoami')

apps/api/src/auth/auth.service.ts

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,37 @@ export class AuthService {
1010
private jwtService: JwtService,
1111
) {}
1212

13-
async adminPasswordLogin(email: string, password: string) {
14-
if (!email) {
15-
throw new BadRequestException('Email is required');
16-
}
17-
18-
if (!password) {
19-
throw new BadRequestException('Password is required');
20-
}
21-
22-
const admin = await this.prismaService.admin.findUnique({
23-
where: {
24-
email,
25-
},
26-
});
27-
28-
if (!admin) {
29-
throw new BadRequestException('User not found');
30-
}
31-
32-
const passwordMatch = await bcrypt.compare(password, admin.password);
33-
34-
if (!passwordMatch) {
35-
throw new BadRequestException('Invalid credentials');
36-
}
37-
38-
const accessToken = this.jwtService.sign({
39-
id: admin.id,
40-
email: admin.email,
41-
role: 'admin',
42-
});
43-
44-
return accessToken;
45-
}
13+
// async adminPasswordLogin(email: string, password: string) {
14+
// if (!email) {
15+
// throw new BadRequestException('Email is required');
16+
// }
17+
18+
// if (!password) {
19+
// throw new BadRequestException('Password is required');
20+
// }
21+
22+
// const admin = await this.prismaService.admin.findUnique({
23+
// where: {
24+
// email,
25+
// },
26+
// });
27+
28+
// if (!admin) {
29+
// throw new BadRequestException('User not found');
30+
// }
31+
32+
// const passwordMatch = await bcrypt.compare(password, admin.password);
33+
34+
// if (!passwordMatch) {
35+
// throw new BadRequestException('Invalid credentials');
36+
// }
37+
38+
// const accessToken = this.jwtService.sign({
39+
// id: admin.id,
40+
// email: admin.email,
41+
// role: 'admin',
42+
// });
43+
44+
// return accessToken;
45+
// }
4646
}

apps/api/src/email/email.controller.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,20 @@ import {
22
Body,
33
Controller,
44
Get,
5-
Param,
65
Post,
76
Query,
87
Res,
98
UseGuards,
109
} from '@nestjs/common';
1110
import { ApiTags } from '@nestjs/swagger';
1211
import { AdminLogAction } from '@prisma/client';
12+
import { Response } from 'express';
1313
import { MemberGuard } from 'src/auth/azure.guard';
1414
import { LoggerService } from 'src/logger/logger.service';
1515

1616
import { EmailsDto } from './dto/emails.dto';
1717
import { EmailsSendDto } from './dto/emailsSend.dto';
1818
import { EmailService } from './email.service';
19-
import { Response } from 'express';
2019

2120
@Controller('email')
2221
@ApiTags('email')
@@ -26,23 +25,6 @@ export class EmailController {
2625
private readonly loggerService: LoggerService,
2726
) {}
2827

29-
@Get('image')
30-
async getImage(@Query('emailId') emailId: string, @Res() res: Response) {
31-
await this.emailService.updateIsSeen(emailId);
32-
33-
const pixel: Buffer = Buffer.from(
34-
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwADfgH+WBwLfwAAAABJRU5ErkJggg==',
35-
'base64',
36-
);
37-
38-
res.writeHead(200, {
39-
'Content-Type': 'image/png',
40-
'Content-Length': pixel.length,
41-
});
42-
43-
res.end(pixel);
44-
}
45-
4628
@UseGuards(MemberGuard)
4729
@Post('send')
4830
async sendEmails(@Body() { emails, text, subject }: EmailsSendDto) {
@@ -60,4 +42,21 @@ export class EmailController {
6042
const templates = await this.emailService.makeEmail(emails, text);
6143
return templates;
6244
}
45+
46+
@Get('image')
47+
async getImage(@Query('emailId') emailId: string, @Res() res: Response) {
48+
await this.emailService.updateIsSeen(emailId);
49+
50+
const pixel: Buffer = Buffer.from(
51+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwADfgH+WBwLfwAAAABJRU5ErkJggg==',
52+
'base64',
53+
);
54+
55+
res.writeHead(200, {
56+
'Content-Type': 'image/png',
57+
'Content-Length': pixel.length,
58+
});
59+
60+
res.end(pixel);
61+
}
6362
}

apps/api/src/email/email.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ import { EmailService } from './email.service';
88
@Module({
99
controllers: [EmailController],
1010
providers: [EmailService, LoggerService, PrismaService],
11+
exports: [EmailService],
1112
})
1213
export class EmailModule {}

apps/api/src/email/email.service.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,25 @@ export class EmailService {
3535
},
3636
});
3737

38+
const createdEmails = await Promise.all(
39+
interns.map((i) => this.createEmailForIntern(i, subject, text)),
40+
);
41+
3842
const template = nunjucks.compile(text);
3943

40-
return Promise.all(
44+
return Promise.allSettled(
4145
interns.map((intern) => {
46+
const emailId = createdEmails.find(
47+
(email) => email.internId === intern.id,
48+
).id;
49+
50+
const trackImage = `<img src="https://internship.dump.hr/api/email/image?emailId=${emailId}" width="1" height="1" style="display:none" />`;
51+
4252
return this.postmark.sendEmail({
4353
From: 'info@dump.hr',
4454
To: intern.email,
4555
Subject: subject,
46-
TextBody: template.render({ intern }),
56+
HtmlBody: `${template.render({ intern })} ${text} ${trackImage}`,
4757
MessageStream: 'outbound',
4858
});
4959
}),
@@ -81,12 +91,33 @@ export class EmailService {
8191
return interns.map((intern) => template.render({ intern }));
8292
}
8393

94+
async createEmailForIntern(
95+
intern: { id: string; email: string },
96+
subject: string,
97+
text: string,
98+
) {
99+
return await this.prisma.email.create({
100+
data: {
101+
internId: intern.id,
102+
subject: subject,
103+
body: text,
104+
isSeen: false,
105+
},
106+
});
107+
}
108+
84109
async updateIsSeen(emailId: string) {
85-
const updated = await this.prisma.email.update({
110+
const email = await this.prisma.email.findUnique({
86111
where: { id: emailId },
87-
data: { isSeen: true },
88112
});
89113

90-
return updated.isSeen;
114+
if (!email) throw new Error('Email not found');
115+
116+
if (!email.isSeen) {
117+
await this.prisma.email.update({
118+
where: { id: emailId },
119+
data: { isSeen: true },
120+
});
121+
}
91122
}
92123
}

0 commit comments

Comments
 (0)