Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
173 commits
Select commit Hold shift + click to select a range
34fe375
feat: add admin automation route
mountain-bit Jul 14, 2026
3619bcb
feat: add Automation main page
mountain-bit Jul 15, 2026
645e664
feat: Add table schema for automation engine
pkrzysiekk Jul 15, 2026
1f3fffe
feat: create automation module and basic types
pkrzysiekk Jul 15, 2026
df51abb
feat:Add create automation repository method
pkrzysiekk Jul 15, 2026
cac2041
feat: Add the rest of automations repository CRUD
pkrzysiekk Jul 15, 2026
fdb2e99
fix: Change ChildStep type
pkrzysiekk Jul 15, 2026
cf520ed
refactor: small adjustments to automations tables
pkrzysiekk Jul 15, 2026
5593007
refactor: Change steps logic
pkrzysiekk Jul 16, 2026
3c4a0f2
Change test
pkrzysiekk Jul 16, 2026
e1e5455
refactor: change automation tables schema
pkrzysiekk Jul 16, 2026
652670f
feat: Add localized text in automations table
pkrzysiekk Jul 16, 2026
752795c
fix: Change description and title in automation table to jsonb format
pkrzysiekk Jul 16, 2026
e1f21d8
fix: adjust automations source types file
pkrzysiekk Jul 16, 2026
3184601
feat: Add migration
pkrzysiekk Jul 16, 2026
80082d9
feat: add automations service
pkrzysiekk Jul 16, 2026
fd499ea
refactor:automation list page
mountain-bit Jul 16, 2026
0ca20d8
feat: add translation
mountain-bit Jul 16, 2026
f104e52
feat: add route
mountain-bit Jul 16, 2026
ac3523e
feat: Add placeholders for hooks and CRUD operations
mountain-bit Jul 16, 2026
c426e19
feat: add Automation Builder page
mountain-bit Jul 16, 2026
a45a2eb
feat: Add CRUD controller for automation
pkrzysiekk Jul 16, 2026
8093f0f
feat: add generated api documentation
pkrzysiekk Jul 16, 2026
6b5e3f6
feat: Add automation trigger table
pkrzysiekk Jul 16, 2026
c6c4c88
feat: add useAutoSave hook
mountain-bit Jul 17, 2026
529298b
refactor: convert AutomationFilters from Tabs to Select
mountain-bit Jul 17, 2026
d618a8a
refactor: add auto-save and UX improvements to AutomationDrawer
mountain-bit Jul 17, 2026
8651de2
refactor: rename condition to trigger in automation builder types
mountain-bit Jul 17, 2026
e80c858
feat: extract shared automation icon map
mountain-bit Jul 17, 2026
e42ca9a
feat: Update automation schema
pkrzysiekk Jul 17, 2026
fef02cc
refactor: Builter UI
mountain-bit Jul 17, 2026
2355d1f
feat: translations
mountain-bit Jul 17, 2026
7c380f2
feat: Builder hooks
mountain-bit Jul 17, 2026
6e5a758
refactor: change pagination
mountain-bit Jul 17, 2026
292dfd7
feat: refactor automation controller
pkrzysiekk Jul 17, 2026
1008b40
fix: import automations service into testing module
pkrzysiekk Jul 17, 2026
4d5026c
fix: export automation controller
pkrzysiekk Jul 17, 2026
3bb01bf
fix: export automations module
pkrzysiekk Jul 17, 2026
ef5e92e
save changes for fixing test
pkrzysiekk Jul 17, 2026
dfae12d
fix: fix controller test
pkrzysiekk Jul 17, 2026
18b4c8a
fix: mock test repository
pkrzysiekk Jul 17, 2026
c20b320
feat: Add repository for automation steps
pkrzysiekk Jul 17, 2026
0b5b609
fear: Add automations steps service
pkrzysiekk Jul 17, 2026
24ba5df
fix: update automation steps declarations
pkrzysiekk Jul 17, 2026
38c522b
fix: fix failing tests
pkrzysiekk Jul 17, 2026
c797f16
feat: Add automation steps controller
pkrzysiekk Jul 17, 2026
61d3cf6
fix: fix failing automation controller test
pkrzysiekk Jul 17, 2026
828b2f1
feat: Add insert guards for automation step insert
pkrzysiekk Jul 17, 2026
bbd1de8
feat: Add basic step validation for insert and update checking
pkrzysiekk Jul 20, 2026
f0aae4d
feat: Add AutomationStep type
pkrzysiekk Jul 20, 2026
cbb0f79
feat: add select course
mountain-bit Jul 20, 2026
067abd9
feat: translations
mountain-bit Jul 20, 2026
ebc460a
feat: save changes before fixing bugs
pkrzysiekk Jul 20, 2026
fd3f358
feat: add hook useCourseUsers
mountain-bit Jul 20, 2026
212dc07
fix: Fix bug allowing to set parentId to a nonexistent stepId
pkrzysiekk Jul 20, 2026
f6e2d27
feat: add course students fetching filtered by mode
mountain-bit Jul 20, 2026
ccbfe56
fix: Remove unnecessary null forgiving operator
pkrzysiekk Jul 20, 2026
5c96c11
feat: Add english translations for automation steps service and repla…
pkrzysiekk Jul 21, 2026
a8718a0
feat: Add missing translation errors for automation service
pkrzysiekk Jul 21, 2026
5f66123
feat: Add bulk insert option to add all automation step at once
pkrzysiekk Jul 21, 2026
4fcc0ee
feat: Add translations for new automation step service errors
pkrzysiekk Jul 21, 2026
d27ae48
chore: add triggers nodes
mountain-bit Jul 21, 2026
b897d3f
chore: add edit action modal
mountain-bit Jul 21, 2026
b3ac385
fix: icon
mountain-bit Jul 21, 2026
0769ef8
chore: add translations
mountain-bit Jul 21, 2026
bc86166
feat: Create automations handler
pkrzysiekk Jul 21, 2026
234fa1c
chore:add provided Variables
mountain-bit Jul 21, 2026
06885b7
feat: change typeContext type in automation steps
pkrzysiekk Jul 21, 2026
f897b64
chore: popup for remove node
mountain-bit Jul 21, 2026
e26932b
chore: next
mountain-bit Jul 21, 2026
16bc9b0
feat: Add automation step repository method to find triggers by name
pkrzysiekk Jul 21, 2026
b3bcae9
Merge remote-tracking branch 'origin/kp_feat_lms_1729_automation_engi…
mountain-bit Jul 22, 2026
122377a
chore: merge
mountain-bit Jul 22, 2026
c268928
fix: Add function to find existing triggers by event name
pkrzysiekk Jul 22, 2026
9ac2b8f
chore: integrate backend with frontend
mountain-bit Jul 22, 2026
63c3c8b
feat: Add automation runner service
pkrzysiekk Jul 22, 2026
8b7a5ab
chore: add triggers type to shared
mountain-bit Jul 22, 2026
ea81071
fix: date format
mountain-bit Jul 22, 2026
520eb86
Merge branch 'kj_feat_1729_Create_automation_engine_page' into kp_fea…
pkrzysiekk Jul 22, 2026
301acd7
feat: Add algorithm executing basic automation step tree
pkrzysiekk Jul 22, 2026
2a94367
feat: Add getting Trigger type with json schema based on what event w…
pkrzysiekk Jul 22, 2026
85f58d9
fix: fix case type error
pkrzysiekk Jul 23, 2026
87f331d
fix: Change automation step insert and update to use toJsonBuildObjec…
pkrzysiekk Jul 23, 2026
41486f8
feat(automation): add automation logs page
mountain-bit Jul 23, 2026
d4e1546
feat: add simulation panel to builder
mountain-bit Jul 23, 2026
0e11343
refactor: extract email templates to constants file
mountain-bit Jul 23, 2026
99e3e41
feat: compute tree positions before saving nodes
mountain-bit Jul 23, 2026
a7191d5
feat: gate draft->active toggle on simulation pass
mountain-bit Jul 23, 2026
f59d1e0
feat: translations
mountain-bit Jul 23, 2026
9691b8d
chore: Add retrieving values from action steps
pkrzysiekk Jul 23, 2026
568be32
feat: provide event type for automation runner
pkrzysiekk Jul 23, 2026
7655bdb
fix: use type form shared
mountain-bit Jul 23, 2026
bc1f6c1
feat: add translations
mountain-bit Jul 23, 2026
7188bfc
chore: Add automation logs table schema
pkrzysiekk Jul 23, 2026
2b07ed2
chore: Apply migration adding automation logs
pkrzysiekk Jul 23, 2026
c488bec
chore: Add repository for automation logs
pkrzysiekk Jul 23, 2026
eff71b7
chore: Add getByAutomationId repository method
pkrzysiekk Jul 23, 2026
5fd181f
fix: fix test
pkrzysiekk Jul 23, 2026
d7bf92a
fix: simulation
mountain-bit Jul 24, 2026
4ac48d4
fix: update import
pkrzysiekk Jul 24, 2026
c335041
chore: Add automation controllers
pkrzysiekk Jul 24, 2026
0c2e3e1
fix: fix automation logs controller test
pkrzysiekk Jul 24, 2026
23af29d
fix: replace service with repository in automation log testing
pkrzysiekk Jul 24, 2026
8700f3c
fix: Add automation runner service test fix
pkrzysiekk Jul 24, 2026
b05288e
feat: add data resolver and template placeholder mapping
mountain-bit Jul 24, 2026
a295cd3
fix: test
mountain-bit Jul 24, 2026
2178def
Merge branch 'kp_feat_lms_1729_automation_engine_backend' of github.c…
mountain-bit Jul 24, 2026
4e1a0b8
Merge branch 'kp_feat_lms_1729_automation_engine_backend' into kj_fea…
mountain-bit Jul 24, 2026
7ac8172
fix: automation builder deletion popup
mountain-bit Jul 24, 2026
a693393
fix: automation logs
mountain-bit Jul 24, 2026
9ca2bc1
fix: add translation
mountain-bit Jul 24, 2026
610e81e
fix: fix fetching automations for dispatching only when their status …
pkrzysiekk Jul 24, 2026
d69e089
chore: Remove commented not used code
pkrzysiekk Jul 24, 2026
35def36
fix: AutomationBuilder
mountain-bit Jul 24, 2026
a3d137c
refactor: Automation page
mountain-bit Jul 24, 2026
21320d8
fix: translation for simulate page
mountain-bit Jul 27, 2026
466583e
fix: translations
mountain-bit Jul 27, 2026
d7354bc
refactor: date-resolver
mountain-bit Jul 27, 2026
948fbfa
fix: error node bug
mountain-bit Jul 27, 2026
126213f
feat: add tests
mountain-bit Jul 27, 2026
3a4a1ad
feat: add tests
mountain-bit Jul 27, 2026
b6e5385
fix: tests
mountain-bit Jul 27, 2026
519bdbb
fix: add automation permision
mountain-bit Jul 27, 2026
63804e8
Merge branch 'kj_feat_1729_Create_automation_engine_page' into kp_fea…
pkrzysiekk Jul 27, 2026
d1125d3
feat: implement email notification templates builder
Jul 23, 2026
789d4b9
fix: feat drizzle migrations
pkrzysiekk Jul 27, 2026
2028f41
merge
mountain-bit Jul 27, 2026
2b9421a
Add action type as const and use it instead of a magic string in swit…
pkrzysiekk Jul 28, 2026
7348010
feat: add tests and enable email templates in automations
mountain-bit Jul 28, 2026
7b290c7
fix: package
mountain-bit Jul 28, 2026
a5865ec
fix: test
mountain-bit Jul 28, 2026
0409fbd
fix: test
mountain-bit Jul 28, 2026
318ce35
test
mountain-bit Jul 28, 2026
6a667be
test
mountain-bit Jul 28, 2026
34bcb8f
Merge branch 'kj_feat_1729_Create_automation_engine_page' into kp_fea…
pkrzysiekk Jul 28, 2026
bcf06dd
fix: get email templates
mountain-bit Jul 28, 2026
78c83dc
fix: send email
mountain-bit Jul 28, 2026
d382405
fix: test
mountain-bit Jul 28, 2026
a8f8c2c
Merge branch 'kj_feat_1729_Create_automation_engine_page' into kp_fea…
pkrzysiekk Jul 29, 2026
0d2ff5f
refactor: Automation
mountain-bit Jul 29, 2026
074f4cd
merge
pkrzysiekk Jul 29, 2026
63cbab1
refeactor: remove prefix from send email action
mountain-bit Jul 29, 2026
827a7bc
fix: test
mountain-bit Jul 29, 2026
c2616c6
fix: test
mountain-bit Jul 29, 2026
067c3b7
Merge branch 'kj_feat_1729_Create_automation_engine_page' into kp_fea…
pkrzysiekk Jul 29, 2026
1bb2ce1
chore: connect logs with handler
pkrzysiekk Jul 29, 2026
db6c44f
feat: add generate default automation button
mountain-bit Jul 29, 2026
5212d75
fix: test
mountain-bit Jul 29, 2026
93c222e
fix: test
mountain-bit Jul 29, 2026
77ee7c7
fix: test
mountain-bit Jul 29, 2026
faa39ad
Merge branch 'kp_feat_lms_1729_automation_engine_backend' into kj_fea…
mountain-bit Jul 29, 2026
f1be59c
feat: implement email notification templates builder
Jul 23, 2026
5e91d32
feat applay feedback
Jul 29, 2026
caa8619
fix: last run column
mountain-bit Jul 29, 2026
54010f6
fix: generation automations language
mountain-bit Jul 29, 2026
3e317f4
fix: test
mountain-bit Jul 29, 2026
26502a4
test: address email template PR check failures
Jul 29, 2026
ab201c3
fix: prepare for merge
pkrzysiekk Jul 29, 2026
97184e5
fix: prepare for merge
pkrzysiekk Jul 29, 2026
5f2d107
merge with staging
pkrzysiekk Jul 29, 2026
3a11e96
Generate automations migrations
pkrzysiekk Jul 29, 2026
28972e0
test: add licensing exceptions
Jul 29, 2026
1b0f24c
fix:test
pkrzysiekk Jul 29, 2026
4eaa58d
test: expande coverage
Jul 29, 2026
131c8ea
make empty button url show warning
Jul 29, 2026
35e0d01
Merge remote-tracking branch 'origin/gw_feat_lms_1719_email_notificat…
pkrzysiekk Jul 30, 2026
a366a1b
fix: remove duplicate package
pkrzysiekk Jul 30, 2026
c625fb5
chore: fix packages conflict
pkrzysiekk Jul 30, 2026
19e194c
chore: fix badly generated automation test
pkrzysiekk Jul 30, 2026
92927c2
fix: add automation button
mountain-bit Jul 30, 2026
c0ea85f
refactor: automations-source.type
mountain-bit Jul 30, 2026
815c8ad
fix: mail logo
mountain-bit Jul 31, 2026
e84f1ea
fix: small things
mountain-bit Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@langfuse/core": "4.2.0",
"@langfuse/otel": "4.2.0",
"@langfuse/tracing": "4.2.0",
"@maily-to/render": "^0.2.3",
"@microsoft/microsoft-graph-client": "3.0.7",
"@nestjs/common": "10.4.17",
"@nestjs/config": "3.2.3",
Expand Down Expand Up @@ -113,6 +114,7 @@
"load-esm": "1.0.3",
"lodash": "4.17.21",
"mammoth": "1.10.0",
"marked": "^18.0.5",
"mime-types": "3.0.2",
"multer": "2.0.2",
"nanoid": "3.3.7",
Expand All @@ -133,6 +135,7 @@
"redis": "4.7.0",
"reflect-metadata": "0.2.0",
"rxjs": "7.8.1",
"sanitize-html": "^2.17.5",
"sharp": "0.34.5",
"slugify": "1.6.6",
"socket.io": "4.8.1",
Expand Down Expand Up @@ -166,6 +169,7 @@
"@types/passport-jwt": "4.0.1",
"@types/passport-local": "1.0.38",
"@types/passport-microsoft": "2.1.0",
"@types/sanitize-html": "^2.16.1",
"@types/supertest": "6.0.2",
"@types/unzipper": "0.10.11",
"@types/uuid": "10.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,15 @@ export class AnnouncementEmailHandler implements IEventHandler<AnnouncementPubli
announcement.baseLanguage,
);
const content = htmlToPlainText(localizedContent);
const { text, html } = this.buildEmail({
const emailTemplate = this.buildEmail({
title,
content,
template: announcement.emailTemplate,
link: this.getButtonLink(tenantOrigin, announcement.emailTemplate, announcement.sourceId),
...defaultEmailSettings,
language: emailLanguage,
});
const [text, html] = await Promise.all([emailTemplate.text, emailTemplate.html]);

await this.emailService.sendEmailWithLogo(
{
Expand Down
67 changes: 67 additions & 0 deletions apps/api/src/announcements/types/automations-source.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Type } from "node_modules/@sinclair/typebox/build/cjs/type";
import { createSelectSchema } from "node_modules/drizzle-typebox/index.cjs";

import { automationSteps } from "src/storage/schema";
import { omitTenantId } from "src/utils/omitTenantId";

import type { AutomationStatus, AutomationType } from "./automations.types";
import type { InferInsertModel, InferSelectModel } from "drizzle-orm";
import type { LocalizedText } from "node_modules/@repo/shared/dist/index.cjs";
import type { Static } from "node_modules/@sinclair/typebox/build/cjs/type";
import type { UUIDType } from "src/common";
import type { automationLogs } from "src/storage/schema";

export type AutomationRecordInput = {
tenantId: UUIDType;
name: LocalizedText;
description: LocalizedText;
status: AutomationStatus;
};

export type AutomationRecordUpdateInput = Partial<AutomationRecordInput>;

export type AutomationStepRecordInput = {
parentId: UUIDType | null;
automationId: UUIDType;
type: AutomationType;
typeContext: TypeContext;
};
export type TypeContext = {
name: string;
providedVariables: Array<{
key: string;
value: unknown;
}>;
};

export type SendEmailActionContext = TypeContext & {
templateId: string;
language?: string;
variableMapping: Record<string, string>;
};

export type AutomationStepUpdateInput = {
type: AutomationType;
typeContext: TypeContext;
};

export type AutomationStepBulkUpdate = {
id: UUIDType;
parentId: UUIDType | null;
automationId: UUIDType;
type: AutomationType;
typeContext: TypeContext;
};

export type AutomationActionStep = {
typeContext: TypeContext;
};
export type AutomationLogRecord = InferSelectModel<typeof automationLogs>;
export type AutomationLogRecordInput = InferInsertModel<typeof automationLogs>;

export const AutomationStepSchema = Type.Omit(omitTenantId(createSelectSchema(automationSteps)), [
"createdAt",
"updatedAt",
]);

export type AutomationStep = Static<typeof AutomationStepSchema>;
60 changes: 60 additions & 0 deletions apps/api/src/announcements/types/automations.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
UserInviteEvent,
UsersImportInviteEmailsEvent,
UserPasswordReminderEvent,
UserWelcomeEvent,
UserFirstLoginEvent,
UsersAssignedToCourseEvent,
UsersShortInactivityEvent,
UsersLongInactivityEvent,
UserChapterFinishedEvent,
UserCourseFinishedEvent,
UserRegisteredEvent,
UserPasswordCreatedEvent,
CourseCompletedEvent,
CertificateExpirationWarningEmailEvent,
CertificateArchivedEmailEvent,
AnnouncementPublishedEvent,
CourseChatUserMentionedEvent,
CourseDueDateReminderEmailEvent,
} from "src/events";

import type { AutomationEventTypes } from "src/automations/handlers/automations-handler";

export enum AutomationStatus {
Enabled = "enabled",
Disabled = "disabled",
Archived = "archived",
Draft = "draft",
}

export const automationTypes = ["action", "condition", "trigger"] as const;

export type AutomationType = (typeof automationTypes)[number];

export const AutomationStepType = {
Action: "action",
Condition: "condition",
Trigger: "trigger",
} as const;

export const AutomationEventNames: Record<AutomationEventTypes["constructor"]["name"], string> = {
[UserInviteEvent.name]: "user_invited",
[UsersImportInviteEmailsEvent.name]: "users_imported_invite",
[UserPasswordReminderEvent.name]: "user_password_reminder",
[UserWelcomeEvent.name]: "user_welcome",
[UserFirstLoginEvent.name]: "user_first_login",
[UsersAssignedToCourseEvent.name]: "users_assigned_to_course",
[UsersShortInactivityEvent.name]: "users_short_inactivity",
[UsersLongInactivityEvent.name]: "users_long_inactivity",
[UserChapterFinishedEvent.name]: "user_chapter_finished",
[UserCourseFinishedEvent.name]: "user_course_finished",
[UserRegisteredEvent.name]: "user_registered",
[UserPasswordCreatedEvent.name]: "user_password_created",
[CourseCompletedEvent.name]: "course_completed",
[CertificateExpirationWarningEmailEvent.name]: "certificate_expiration_warning",
[CertificateArchivedEmailEvent.name]: "certificate_archived",
[AnnouncementPublishedEvent.name]: "announcement_published",
[CourseChatUserMentionedEvent.name]: "course_chat_user_mentioned",
[CourseDueDateReminderEmailEvent.name]: "course_due_date_reminder",
};
8 changes: 8 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { AuthModule } from "./auth/auth.module";
import { GoogleStrategy } from "./auth/strategy/google.strategy";
import { MicrosoftStrategy } from "./auth/strategy/microsoft.strategy";
import { SlackStrategy } from "./auth/strategy/slack.strategy";
import { AutomationsModule } from "./automations/automations.module";
import { BunnyStreamModule } from "./bunny/bunnyStream.module";
import { CacheModule } from "./cache/cache.module";
import { CalendarModule } from "./calendar/calendar.module";
Expand All @@ -48,6 +49,8 @@ import { PermissionsGuard } from "./common/guards/permissions.guard";
import { StagingGuard } from "./common/guards/staging.guard";
import { CourseChatModule } from "./course-chat/course-chat.module";
import { CourseModule } from "./courses/course.module";
import { PublicCourseThumbnailModule } from "./courses/public-course-thumbnail.module";
import { EmailNotificationTemplatesModule } from "./email-notification-templates/email-templates.module";
import { EventsModule } from "./events/events.module";
import { FileModule } from "./file/files.module";
import { GlobalSearchModule } from "./global-search/global-search.module";
Expand All @@ -63,6 +66,7 @@ import { LumaModule } from "./luma/luma.module";
import { NewsModule } from "./news/news.module";
import { OutboxModule } from "./outbox/outbox.module";
import { PermissionsModule } from "./permissions/permissions.module";
import { PublicEmailTemplateImageModule } from "./public-email-template-image/public-email-template-image.module";
import { QuestionsModule } from "./questions/question.module";
import { AppThrottlerGuard } from "./rate-limit/app-throttler.guard";
import { RedisThrottlerStorage } from "./rate-limit/redis-throttler.storage";
Expand Down Expand Up @@ -168,6 +172,9 @@ import type { RedisClient } from "src/redis";
ScormModule,
CertificatesModule,
AnnouncementsModule,
EmailNotificationTemplatesModule,
PublicCourseThumbnailModule,
PublicEmailTemplateImageModule,
IngestionModule,
IntegrationModule,
LearningTimeModule,
Expand All @@ -185,6 +192,7 @@ import type { RedisClient } from "src/redis";
LumaModule,
LiveTrainingModule,
CalendarModule,
AutomationsModule,
],
controllers: [],
providers: [
Expand Down
10 changes: 6 additions & 4 deletions apps/api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ export class AuthService {
email: string,
oldTokenHash: string,
createToken: string,
emailTemplate: { text: string; html: string },
emailTemplate: { text: Promise<string> | string; html: Promise<string> | string },
expiryDate: Date,
reminderCount: number,
) {
Expand All @@ -695,12 +695,14 @@ export class AuthService {
userId,
);

const [text, html] = await Promise.all([emailTemplate.text, emailTemplate.html]);

await this.emailService.sendEmailWithLogo(
{
to: email,
subject: getEmailSubject("passwordReminderEmail", defaultEmailSettings.language),
text: emailTemplate.text,
html: emailTemplate.html,
text,
html,
},
{ tenantId },
);
Expand Down Expand Up @@ -876,7 +878,7 @@ export class AuthService {
...defaultEmailSettings,
});

const { html, text } = magicLinkEmail;
const [text, html] = await Promise.all([magicLinkEmail.text, magicLinkEmail.html]);

await this.emailService.sendEmailWithLogo(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Test } from "@nestjs/testing";

import { BaseResponse } from "src/common";

import { AutomationLogsRepository } from "../repositories/automation-logs/automation-logs";

import { AutomationLogsController } from "./automation-logs.controller";

import type { TestingModule } from "@nestjs/testing";
import type { UUIDType } from "src/common";

describe("AutomationLogsController", () => {
let controller: AutomationLogsController;
let repository: jest.Mocked<AutomationLogsRepository>;

const automationId = "auto-1" as UUIDType;

const mockLogs = [
{ id: "log-1", automationId, status: "success", createdAt: "2025-07-01" },
{ id: "log-2", automationId, status: "failed", createdAt: "2025-07-02" },
];

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AutomationLogsController],
providers: [
{
provide: AutomationLogsRepository,
useValue: {
getAll: jest.fn(),
GetByAutomationId: jest.fn(),
},
},
],
}).compile();

controller = module.get(AutomationLogsController);
repository = module.get(AutomationLogsRepository);
});

afterEach(() => {
jest.clearAllMocks();
});

describe("getAll", () => {
it("returns all logs wrapped in BaseResponse", async () => {
repository.getAll.mockResolvedValue(mockLogs as any);

const result = await controller.getAll();

expect(repository.getAll).toHaveBeenCalled();
expect(result).toBeInstanceOf(BaseResponse);
expect(result.data).toEqual(mockLogs);
});

it("returns empty array when no logs exist", async () => {
repository.getAll.mockResolvedValue([]);

const result = await controller.getAll();

expect(result.data).toEqual([]);
});
});

describe("getByAutomationId", () => {
it("returns logs for specific automation wrapped in BaseResponse", async () => {
repository.GetByAutomationId.mockResolvedValue(mockLogs as any);

const result = await controller.getByAutomationId(automationId);

expect(repository.GetByAutomationId).toHaveBeenCalledWith(automationId);
expect(result).toBeInstanceOf(BaseResponse);
expect(result.data).toEqual(mockLogs);
});

it("returns empty array when no logs for automation", async () => {
repository.GetByAutomationId.mockResolvedValue([]);

const result = await controller.getByAutomationId(automationId);

expect(result.data).toEqual([]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Controller, Get, Param } from "@nestjs/common";
import { PERMISSIONS } from "@repo/shared";

import { BaseResponse, UUIDType } from "src/common";
import { RequirePermission } from "src/common/decorators/require-permission.decorator";

import { AutomationLogsRepository } from "../repositories/automation-logs/automation-logs";

@RequirePermission(PERMISSIONS.AUTOMATION_MANAGE)
@Controller("automation-logs")
export class AutomationLogsController {
constructor(private readonly automationLogsRepository: AutomationLogsRepository) {}

@Get()
async getAll() {
const logs = await this.automationLogsRepository.getAll();
return new BaseResponse(logs);
}

@Get("automation/:automationId")
async getByAutomationId(@Param("automationId") automationId: UUIDType) {
const logs = await this.automationLogsRepository.GetByAutomationId(automationId);
return new BaseResponse(logs);
}
}
Loading
Loading