Initial commit: Angular frontend and Expressjs backend

This commit is contained in:
chamikaJ
2024-05-17 09:32:30 +05:30
parent eb0a0d77d6
commit 298ca6beeb
3548 changed files with 193558 additions and 3 deletions

View File

@@ -0,0 +1,58 @@
import {CronJob} from "cron";
import moment from "moment";
import db from "../config/db";
import {IDailyDigest} from "../interfaces/daily-digest";
import {sendDailyDigest} from "../shared/email-notifications";
import {log_error} from "../shared/utils";
import {getBaseUrl, mapTeams} from "./helpers";
// At 11:00+00 (4.30pm+530) on every day-of-month if it's on every day-of-week from Monday through Friday.
const TIME = "0 11 */1 * 1-5";
// const TIME = "0/30 * * * *";
// const TIME = "* * * * *";
const log = (value: any) => console.log("daily-digest-cron-job:", value);
async function onDailyDigestJobTick() {
try {
log("(cron) Daily digest job started.");
const q = "SELECT get_daily_digest() AS digest;";
const result = await db.query(q, []);
const [fn] = result.rows;
const dataset: IDailyDigest[] = fn.digest || [];
let sentCount = 0;
for (const digest of dataset) {
digest.greeting = `Hi ${digest.name},`;
digest.note = `Here's your ${moment().format("dddd")} update!`;
digest.base_url = `${getBaseUrl()}/worklenz`;
digest.settings_url = `${getBaseUrl()}/worklenz/settings/notifications`;
digest.recently_assigned = mapTeams(digest.recently_assigned);
digest.overdue = mapTeams(digest.overdue);
digest.recently_completed = mapTeams(digest.recently_completed);
if (digest.recently_assigned?.length || digest.overdue?.length || digest.recently_completed?.length) {
sentCount++;
void sendDailyDigest(digest.email as string, digest);
}
}
log(`(cron) Daily digest job ended with ${sentCount} emails.`);
} catch (error) {
log_error(error);
log("(cron) Daily digest job ended with errors.");
}
}
export function startDailyDigestJob() {
log("(cron) Daily digest job ready.");
const job = new CronJob(
TIME,
() => void onDailyDigestJobTick(),
() => log("(cron) Daily Digest job successfully executed."),
true
);
job.start();
}

View File

@@ -0,0 +1,75 @@
import {ITaskAssignmentModelProject, ITaskAssignmentModelTeam} from "../interfaces/task-assignments-model";
import {isLocalServer} from "../shared/utils";
export function mapMembersWithAnd(members: string) {
const $members = members.split(",").map(m => m.trim());
if ($members.length > 1) {
const last = $members.pop();
const end = last ? ` and ${last}` : "";
return `${$members.join(", ")}${end}`;
}
return "";
}
export function getBaseUrl() {
if (isLocalServer()) return `http://${process.env.HOSTNAME}`;
return `https://${process.env.HOSTNAME}`;
}
function mapMembers(project: ITaskAssignmentModelProject) {
for (const task of project.tasks || []) {
if (task.members)
task.members = mapMembersWithAnd(task.members);
}
}
function updateUrls(project: ITaskAssignmentModelProject) {
project.url = `${getBaseUrl()}/worklenz/projects/${project.id}`;
if (project.tasks) {
project.tasks = project.tasks.map(task => {
if (task.id)
task.url = `${project.url}?task=${task.id}`;
return task;
});
}
}
export function mapTeams(data?: ITaskAssignmentModelTeam[]) {
if (!data) return [];
const result = [];
for (const item of data || []) {
const projects = item.projects?.filter(project => project.tasks?.length);
for (const project of projects || []) {
if (project.id) {
mapMembers(project);
updateUrls(project);
}
}
if (projects?.length) {
item.projects = projects;
result.push(item);
}
}
return result;
}
export function mapProjects(data?: ITaskAssignmentModelTeam[]) {
if (!data) return [];
const result = [];
for (const item of data || []) {
const projects = item.projects?.filter(project => project.tasks?.length);
for (const project of projects || []) {
if (project.id) {
mapMembers(project);
updateUrls(project);
result.push(project);
}
}
}
return result;
}

View File

@@ -0,0 +1,9 @@
import {startDailyDigestJob} from "./daily-digest-job";
import {startNotificationsJob} from "./notifications-job";
import {startProjectDigestJob} from "./project-digest-job";
export function startCronJobs() {
startNotificationsJob();
startDailyDigestJob();
startProjectDigestJob();
}

View File

@@ -0,0 +1,68 @@
// https://www.npmjs.com/package/cron
// https://crontab.guru/#0_22_*/1_*_*
import {CronJob} from "cron";
import db from "../config/db";
import {ITaskAssignmentsModel} from "../interfaces/task-assignments-model";
import {sendAssignmentUpdate} from "../shared/email-notifications";
import {log_error} from "../shared/utils";
import {getBaseUrl, mapProjects} from "./helpers";
const TIME = "*/10 * * * *";
const log = (value: any) => console.log("notifications-cron-job:", value);
function getModel(model: ITaskAssignmentsModel): ITaskAssignmentsModel {
const mappedModel: ITaskAssignmentsModel = {...model};
mappedModel.name = mappedModel.name?.split(" ")[0] || "";
mappedModel.url = `${getBaseUrl()}/worklenz/team/member/${mappedModel.team_member_id}`;
mappedModel.settings_url = `${getBaseUrl()}/worklenz/settings/notifications`;
const teams = [];
for (const team of model.teams || []) {
team.projects = mapProjects([team]);
if (team.projects.length)
teams.push(team);
}
mappedModel.teams = teams;
return mappedModel;
}
async function onNotificationJobTick() {
try {
log("(cron) Notifications job started.");
const q = "SELECT get_task_updates() AS updates;";
const result = await db.query(q, []);
const [data] = result.rows;
const updates = (data.updates || []) as ITaskAssignmentsModel[];
let sentCount = 0;
for (const item of updates) {
if (item.email) {
const model = getModel(item);
if (model.teams?.length) {
sentCount++;
void sendAssignmentUpdate(item.email, model);
}
}
}
log(`(cron) Notifications job ended with ${sentCount} emails.`);
} catch (error) {
log_error(error);
log("(cron) Notifications job ended with errors.");
}
}
export function startNotificationsJob() {
log("(cron) Email notifications job ready.");
const job = new CronJob(
TIME,
() => void onNotificationJobTick(),
() => log("(cron) Notifications job successfully executed."),
true
);
job.start();
}

View File

@@ -0,0 +1,70 @@
import {CronJob} from "cron";
import db from "../config/db";
import {log_error} from "../shared/utils";
import {getBaseUrl} from "./helpers";
import {IProjectDigest, IProjectDigestTask} from "../interfaces/project-digest";
import {sendProjectDailyDigest} from "../shared/email-notifications";
// At 11:00+00 (4.30pm+530) on every day-of-month if it's on every day-of-week from Monday through Friday.
const TIME = "0 11 */1 * 1-5";
// const TIME = "* * * * *";
const log = (value: any) => console.log("project-digest-cron-job:", value);
function updateTaskUrls(projectId: string, tasks: IProjectDigestTask[]) {
const baseUrl = getBaseUrl();
for (const task of tasks) {
task.url = `${baseUrl}/worklenz/projects/${projectId}?tab=tasks-list&task=${task.id}`;
}
}
function updateMetadata(project: IProjectDigest, subscriberName: string) {
project.greeting = `Hi ${subscriberName},`;
project.summary = `Here's the "${project.name}" summary | ${project.team_name}`;
project.settings_url = `${getBaseUrl()}/worklenz/settings/notifications`;
project.project_url = `${getBaseUrl()}/worklenz/projects/${project.id}?tab=tasks-list`;
}
async function onProjectDigestJobTick() {
try {
log("(cron) Daily digest job started.");
const q = "SELECT get_project_daily_digest() AS digest;";
const result = await db.query(q, []);
const [fn] = result.rows;
const dataset: IProjectDigest[] = fn.digest || [];
let sentCount = 0;
for (const project of dataset) {
for (const subscriber of project.subscribers) {
updateMetadata(project, subscriber.name);
updateTaskUrls(project.id, project.today_completed);
updateTaskUrls(project.id, project.today_new);
updateTaskUrls(project.id, project.due_tomorrow);
if (subscriber.email) {
sentCount++;
void sendProjectDailyDigest(subscriber.email, project);
}
}
}
log(`(cron) Project digest job ended with ${sentCount} emails.`);
} catch (error) {
log_error(error);
log("(cron) Project digest job ended with errors.");
}
}
export function startProjectDigestJob() {
log("(cron) Project digest job ready.");
const job = new CronJob(
TIME,
() => void onProjectDigestJobTick(),
() => log("(cron) Project Digest job successfully executed."),
true
);
job.start();
}