emailer and resume start
This commit is contained in:
@@ -240,6 +240,10 @@ th.left, td.left {
|
||||
color: var(--dull-primary-color);
|
||||
}
|
||||
|
||||
.dull-primary-border-color {
|
||||
border-color: var(--dull-primary-color);
|
||||
}
|
||||
|
||||
.primary-text-color {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type Posting,
|
||||
type Application
|
||||
} from '$lib/types';
|
||||
import { sendEmployerNotificationEmail } from '$lib/emailer.server';
|
||||
|
||||
export async function createUser(user: User): Promise<number> {
|
||||
const password_hash: string = await bcrypt.hash(user.password!, 12);
|
||||
@@ -700,6 +701,9 @@ export async function createApplication(application: Application): Promise<numbe
|
||||
RETURNING id;
|
||||
`;
|
||||
|
||||
sendEmployerNotificationEmail(application.postingId).catch((err) => {
|
||||
console.error('Failed to send employer notification email: ', err);
|
||||
});
|
||||
return response[0].id;
|
||||
}
|
||||
|
||||
@@ -764,3 +768,32 @@ export async function setUserCompanyId(userId: number, companyId: number): Promi
|
||||
WHERE id = ${userId};
|
||||
`;
|
||||
}
|
||||
|
||||
export async function getNotificationInfo(
|
||||
postingId: number
|
||||
): Promise<{ title: string; emails: string[] }> {
|
||||
const data = await sql`
|
||||
WITH posting_data AS (
|
||||
SELECT title, company_id
|
||||
FROM postings
|
||||
WHERE id = ${postingId}
|
||||
),
|
||||
user_emails AS (
|
||||
SELECT email
|
||||
FROM users
|
||||
WHERE company_id = (SELECT company_id FROM posting_data)
|
||||
)
|
||||
SELECT
|
||||
(SELECT title FROM posting_data) AS title,
|
||||
(SELECT json_agg(email) FROM user_emails) AS emails;
|
||||
`;
|
||||
|
||||
if (!data || !data[0]) {
|
||||
error(404, 'Posting not found');
|
||||
}
|
||||
|
||||
return {
|
||||
title: data[0].title,
|
||||
emails: data[0].emails
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Import the Nodemailer library
|
||||
import nodemailer from 'nodemailer';
|
||||
import { getNotificationInfo } from '$lib/db/index.server';
|
||||
|
||||
// Create a transporter object using SMTP transport
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: Number(process.env.EMAIL_PORT),
|
||||
secure: true, // true for 465, false for other ports
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
||||
export async function sendEmployerNotificationEmail(postingId: number) {
|
||||
let info = await getNotificationInfo(postingId);
|
||||
// Send mail with defined transport object
|
||||
await transporter.sendMail({
|
||||
from: `CareerConnect Notifications <${process.env.EMAIL_USER}>`,
|
||||
// to: info.emails.join(', '), // EMAILING OF REAL COMPANIES DISABLED, UNCOMMENT TO ENABLE
|
||||
to: 'drake@marinodev.com', // TEMPORARY EMAIL FOR TESTING
|
||||
subject: 'New Application Received!',
|
||||
text: `A new application has been received for the posting '${info.title}'!\n\nCheck it out at ${process.env.BASE_URL}/postings/${postingId}/manage/applications`
|
||||
});
|
||||
}
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..40,400,0,0&display=block&icon_names=account_circle,arrow_drop_down,arrow_drop_up,calendar_today,call,check,close,dark_mode,delete,description,edit,group,info,light_mode,login,mail,person,search,sell,store,visibility,visibility_off,work"
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..40,400,0,0&display=block&icon_names=account_circle,arrow_drop_down,arrow_drop_up,calendar_today,call,check,close,dark_mode,delete,description,edit,group,info,light_mode,login,mail,open_in_new,person,search,sell,store,upload,visibility,visibility_off,work"
|
||||
/>
|
||||
|
||||
<div class="flex min-h-screen flex-col">
|
||||
|
||||
@@ -2,10 +2,20 @@ import type { PageServerLoad } from './$types';
|
||||
import { deleteApplicationWithUser, getUserWithCompanyAndApplications } from '$lib/db/index.server';
|
||||
import { getUserId } from '$lib/index.server';
|
||||
import { type Actions, fail } from '@sveltejs/kit';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
const id = getUserId(cookies);
|
||||
return await getUserWithCompanyAndApplications(id);
|
||||
const userData = await getUserWithCompanyAndApplications(id);
|
||||
const resumeExists = fs.existsSync(
|
||||
path.join(process.cwd(), 'static', 'uploads', 'resume', `${id}.pdf`)
|
||||
);
|
||||
|
||||
return {
|
||||
...userData,
|
||||
resumeExists
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
if (!document.cookie.includes('jwt=')) {
|
||||
window.location.href = '/signin';
|
||||
}
|
||||
if (window.location.search.includes('refresh')) {
|
||||
location.replace(location.pathname);
|
||||
}
|
||||
});
|
||||
|
||||
const dateFormatOptions: Intl.DateTimeFormatOptions = {
|
||||
@@ -39,6 +42,7 @@
|
||||
document.getElementById('deleteConfirmModal')!.style.display = 'none';
|
||||
}
|
||||
|
||||
let resumeUpload;
|
||||
let { data, form }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
@@ -136,6 +140,25 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<div class="font-semibold">Résumé:</div>
|
||||
{#if data.resumeExists}
|
||||
<a class="pb-2" href="/uploads/resumes/{data.user.id}.pdf">
|
||||
<button class="dull-primary-bg-color rounded-md px-2.5 py-1"
|
||||
><span class="material-symbols-outlined align-middle">open_in_new</span> View résumé</button
|
||||
>
|
||||
</a>
|
||||
<input type="file" accept=".pdf" />
|
||||
<button class="ml-2 rounded-md border px-2.5 py-1"
|
||||
><span class="material-symbols-outlined align-middle">upload</span> Upload new version</button
|
||||
>
|
||||
{:else}
|
||||
<!-- <div class="">No résumé submitted.</div>-->
|
||||
<button class="dull-primary-bg-color mb-2 rounded-md px-2.5 py-1"
|
||||
><span class="material-symbols-outlined align-middle">upload</span> Upload</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="top-border pt-2 font-semibold">
|
||||
Permissions: <span class="font-normal">{data.user.perms}</span>
|
||||
</div>
|
||||
@@ -148,7 +171,10 @@
|
||||
<div class="top-border flex justify-between p-3">
|
||||
<div class="inline-block">
|
||||
<div class="font-semibold">
|
||||
Applied to: <span class="font-normal">{application.postingTitle}</span>
|
||||
Applied to: <a
|
||||
class="hover-hyperlink font-normal"
|
||||
href="/postings/{application.postingId}">{application.postingTitle}</a
|
||||
>
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
Applied on: <span class="font-normal"
|
||||
|
||||
@@ -122,6 +122,8 @@
|
||||
class="dull-primary-bg-color inline-block h-min rounded-md px-2.5 py-1 align-top"
|
||||
href="/postings/{details.id}/apply">Apply</a
|
||||
>
|
||||
{:else}
|
||||
Sign-in to apply
|
||||
{/if}
|
||||
</div>
|
||||
<div class="scrollbar-on-elevated details-height overflow-y-scroll">
|
||||
|
||||
@@ -97,8 +97,9 @@
|
||||
></textarea>
|
||||
</div>
|
||||
<p>
|
||||
Your account information will be submitted along with this application. If there is any
|
||||
other information you would like the employer to know, please add it in the box above.
|
||||
Your account information and résumé (if supplied) will be submitted along with this
|
||||
application. If there is any other information you would like the employer to know, please
|
||||
add it in the box above.
|
||||
</p>
|
||||
|
||||
{#if form?.errorMessage}
|
||||
|
||||
@@ -30,7 +30,7 @@ export const actions: Actions = {
|
||||
setJWT(cookies, user);
|
||||
await updateLastSignin(username);
|
||||
// redirect to home page
|
||||
throw redirect(303, '/account');
|
||||
throw redirect(303, '/account?refresh=true');
|
||||
} else {
|
||||
return fail(400, { errorMessage: 'Missing username or password' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user