diff --git a/apps/api/package.json b/apps/api/package.json index 9243dba6..bb6b15c7 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -25,6 +25,7 @@ "@nestjs/jwt": "^10.1.1", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^9.0.0", + "@nestjs/schedule": "^6.0.1", "@nestjs/serve-static": "^4.0.0", "@nestjs/swagger": "^7.1.10", "@prisma/client": "^5.2.0", diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 0694c856..6a2117c4 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; import { ServeStaticModule } from '@nestjs/serve-static'; import { extname, join } from 'path'; @@ -60,6 +61,7 @@ import { TestSlotModule } from './test-slot/test-slot.module'; QuestionModule, InternshipApplicationStatusModule, OldInternResultModule, + ScheduleModule.forRoot(), ], controllers: [AppController], providers: [AppService, PrismaService], diff --git a/apps/api/src/email/email.service.ts b/apps/api/src/email/email.service.ts index 259469a5..32cb6028 100644 --- a/apps/api/src/email/email.service.ts +++ b/apps/api/src/email/email.service.ts @@ -1,11 +1,21 @@ +import { + BoardActionType, + Intern, + InterviewStatus, +} from '@internship-app/types'; import { Injectable } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; import * as nunjucks from 'nunjucks'; import * as postmark from 'postmark'; +import { InternService } from 'src/intern/intern.service'; import { PrismaService } from 'src/prisma.service'; @Injectable() export class EmailService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly internService: InternService, + ) {} private postmark = new postmark.ServerClient(process.env.POSTMARK_API_TOKEN); @@ -47,6 +57,7 @@ export class EmailService { (email) => email.internId === intern.id, ).id; const trackImage = ``; + return this.postmark.sendEmail({ From: 'info@dump.hr', To: intern.email, @@ -123,4 +134,51 @@ export class EmailService { }); } } + + @Cron(CronExpression.EVERY_DAY_AT_8AM, { timeZone: 'UTC+2' }) + async sendInterviewInvitations() { + const newInterns: Intern[] = await this.prisma.$queryRaw` + select i.email + from "Intern" i + left join "InternDiscipline" id on i.id = id."internId" + where i."createdAt" >= NOW() - INTERVAL '1 day' + and id."discipline" <> 'Development' + and id.status = 'Pending' + and i."interviewStatus" = 'Pending' + `; + + const ids = newInterns.map((newIntern) => newIntern.id); + + this.internService.applyBoardAction( + { + actionType: BoardActionType.SetInterviewStatus, + interviewStatus: InterviewStatus.PickTerm, + }, + ids, + ); + + const emails = newInterns.map((intern) => intern.email); + + this.sendEmail( + emails, + `Vezano za tvoju prijavu na ovogodišnji DUMP Internship, pozivamo te da sudjeluješ u prvom krugu intervjua koji će se održati kroz ovaj tjedan. Ako ti ne odgovara ni jedan od ponuđenih termina, ne brini - vremenom ćemo ih dodati još. + Pripazi; kako vrijeme bude prolazilo, bit će sve manja ponuda termina. + Ovo su tvoja prijavljena područja: + Ukoliko želiš promijeniti prijavljena područja, javi nam se na info@dump.hr, i to prije nego što odabereš intervju termin. + Molimo te da u nastavku izabereš termin koji ti najviše odgovara prateći sljedeće upute: + - Otvori svoju status stranivu iz prethodnog maila + - Odaberi jedan od ponuđenih dostupnih datuma, nakon čega ti se otvore dostupni termini + - Odaberi jedan od ponuđenih termina, nakon čega ti se otvori potvrdna forma + - Klik na confirm i tvoj termin je uspješno rezerviran! + Tvoj intervju će se održati u odabranom terminu u našem uredu (prostorija A223) na FESB-u (Ruđera Boškovića 32). + Naš ured ćeš pronaći tako da kad uđeš kroz glavna vrata FESB-a skreneš desno do kraja hodnika (put referade) dok ne dođeš do stepenica koje su s lijeve strane. Popneš se stepenicama na prvi kat i skreneš lijevo. Nastaviš hodnikom do kraja i s desne strane vidjet ćeš vrata našeg ureda (A223). + Važno: + Molimo te da ne ulaziš u ured dok te ne pozovemo, nego da čekaš ispred. + Ako kojim slučajem ne možeš doći, moguće je intervju organizirati videopozivom uz prethodnu najavu nemogućnosti dolaska. + Međutim, dojam je uvijek bolji uživo! + Vidimo se! + DUMP Udruga mladih programera`, + 'Poziv na dump internrship intervju', + ); + } } diff --git a/apps/api/src/intern/intern.module.ts b/apps/api/src/intern/intern.module.ts index 43d9542e..c7f49683 100644 --- a/apps/api/src/intern/intern.module.ts +++ b/apps/api/src/intern/intern.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { EmailService } from 'src/email/email.service'; +import { EmailModule } from 'src/email/email.module'; import { LoggerService } from 'src/logger/logger.service'; import { PrismaService } from 'src/prisma.service'; @@ -9,10 +9,10 @@ import { MicrosoftGraphService } from 'src/interview-slot/graph.service'; @Module({ controllers: [InternController], + imports: [EmailModule], providers: [ InternService, LoggerService, - EmailService, MicrosoftGraphService, PrismaService, ], diff --git a/apps/api/src/interview-slot/interview-slot.module.ts b/apps/api/src/interview-slot/interview-slot.module.ts index 7412cc46..90d43a0d 100644 --- a/apps/api/src/interview-slot/interview-slot.module.ts +++ b/apps/api/src/interview-slot/interview-slot.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; import { EmailModule } from 'src/email/email.module'; -import { EmailService } from 'src/email/email.service'; import { LoggerService } from 'src/logger/logger.service'; import { PrismaService } from 'src/prisma.service'; @@ -14,7 +13,6 @@ import { InterviewSlotService } from './interview-slot.service'; providers: [ InterviewSlotService, LoggerService, - EmailService, MicrosoftGraphService, PrismaService, ], diff --git a/apps/api/src/test-slot/test-slot.module.ts b/apps/api/src/test-slot/test-slot.module.ts index a466feba..ce099435 100644 --- a/apps/api/src/test-slot/test-slot.module.ts +++ b/apps/api/src/test-slot/test-slot.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { EmailService } from 'src/email/email.service'; +import { EmailModule } from 'src/email/email.module'; import { LoggerService } from 'src/logger/logger.service'; import { PrismaService } from 'src/prisma.service'; @@ -8,6 +8,7 @@ import { TestSlotService } from './test-slot.service'; @Module({ controllers: [TestSlotController], - providers: [TestSlotService, LoggerService, PrismaService, EmailService], + providers: [TestSlotService, LoggerService, PrismaService], + imports: [EmailModule], }) export class TestSlotModule {} diff --git a/yarn.lock b/yarn.lock index b0e4ca83..d1a4003e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1397,6 +1397,13 @@ multer "1.4.4-lts.1" tslib "2.5.3" +"@nestjs/schedule@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@nestjs/schedule/-/schedule-6.0.1.tgz#048b34e5c16d2d9fa14a9df2bfa2d3b7fa8ef168" + integrity sha512-v3yO6cSPAoBSSyH67HWnXHzuhPhSNZhRmLY38JvCt2sqY8sPMOODpcU1D79iUMFf7k16DaMEbL4Mgx61ZhiC8Q== + dependencies: + cron "4.3.3" + "@nestjs/schematics@^9.0.0", "@nestjs/schematics@^9.0.4": version "9.2.0" resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-9.2.0.tgz#f840054b5ae4b0b4e70aa9f72c09c3cf388f2512" @@ -2194,6 +2201,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.199.tgz#c3edb5650149d847a277a8961a7ad360c474e9bf" integrity sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg== +"@types/luxon@~3.7.0": + version "3.7.1" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.7.1.tgz#ef51b960ff86801e4e2de80c68813a96e529d531" + integrity sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg== + "@types/mime@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" @@ -3368,6 +3380,14 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cron@4.3.3: + version "4.3.3" + resolved "https://registry.yarnpkg.com/cron/-/cron-4.3.3.tgz#d37cfcbc73ba34a50d9d9ce9b653ae60837377d7" + integrity sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw== + dependencies: + "@types/luxon" "~3.7.0" + luxon "~3.7.0" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -4910,6 +4930,11 @@ luxon@^3.2.1: resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.3.tgz#8ddf0358a9492267ffec6a13675fbaab5551315d" integrity sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg== +luxon@~3.7.0: + version "3.7.2" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.7.2.tgz#d697e48f478553cca187a0f8436aff468e3ba0ba" + integrity sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew== + macos-release@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.1.tgz#bccac4a8f7b93163a8d163b8ebf385b3c5f55bf9"