FBLA25/src/lib/db/index.server.ts
drake 77f3d182d4
All checks were successful
ci / docker_image (push) Successful in 1m41s
ci / deploy (push) Successful in 17s
Resume Logic
2025-03-28 11:16:15 -05:00

901 lines
24 KiB
TypeScript

import bcrypt from 'bcrypt';
import sql from '$lib/db/db.server';
import { error } from '@sveltejs/kit';
import { deleteLogo, saveAvatar, saveLogo } from '$lib/index.server';
import {
EmploymentType,
type User,
type Company,
type Tag,
type Posting,
type Application
} from '$lib/types';
import { sendEmployerNotificationEmail } from '$lib/emailer.server';
import fs from 'fs';
import path from 'path';
export async function createUser(user: User): Promise<number> {
const password_hash: string = await bcrypt.hash(user.password!, 12);
const response = await sql`
INSERT INTO users (username, password_hash, perms, created_at, last_signin, active, email, phone, full_name, company_code)
VALUES (${user.username}, ${password_hash}, ${user.perms}, NOW(), NOW(), ${user.active}, ${user.email}, ${user.phone}, ${user.fullName}, ${user.companyCode})
RETURNING id;
`;
// TODO: handle custom image uploads
user.id = response[0].id;
await saveAvatar(user);
return response[0].id;
}
export async function updateUser(user: User): Promise<number> {
let password_hash: string | null = null;
// Hash the password if provided
if (user.password) {
password_hash = await bcrypt.hash(user.password, 12);
}
// Construct the SQL query
const response = await sql`UPDATE users
SET
username = ${user.username},
${user.perms !== undefined ? sql`perms = ${user.perms},` : sql``}
${user.active !== undefined ? sql`active = ${user.active},` : sql``}
${password_hash !== null ? sql`password_hash = ${password_hash},` : sql``}
email = ${user.email},
phone = ${user.phone},
full_name = ${user.fullName},
company_code = ${user.companyCode}
WHERE id = ${user.id}
RETURNING id;`;
await saveAvatar(user);
return response[0].id;
}
export async function checkUserCreds(username: string, password: string): Promise<User | null> {
const [user] = await sql`
SELECT id, username, password_hash, perms, active, company_id AS "companyId"
FROM users
WHERE username = ${username}
`;
if (!user) {
return null;
}
if (await bcrypt.compare(password, user.password_hash)) {
delete user.password_hash;
return <User>user;
}
return null;
}
// should require MANAGE_USERS permission
export async function getUsers(searchQuery: string | null = null): Promise<User[]> {
const users = await sql`
SELECT id,
username,
perms,
created_at AT TIME ZONE 'UTC' AS "createdAt",
last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
active,
email,
phone,
full_name AS "fullName",
company_id
FROM users
WHERE username ILIKE ${searchQuery ? `%${searchQuery}%` : '%'};
`;
users.forEach((user) => {
user.company = {
id: user.company_id
};
delete user.company_id;
});
return <User[]>(<unknown>users);
}
export async function getCompanies(searchQuery: string | null = null): Promise<Company[]> {
return sql<Company[]>`
SELECT id,
name,
description,
website,
company_code AS "companyCode",
created_at AT TIME ZONE 'UTC' AS "createdAt"
FROM companies
WHERE name ILIKE ${searchQuery ? `%${searchQuery}%` : '%'};
`;
}
// should require MANAGE_USERS permission
export async function getUser(id: number): Promise<User> {
const [user] = await sql`
SELECT id, username, perms,
created_at AT TIME ZONE 'UTC' AS "createdAt",
last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
active, email, phone, full_name AS "fullName", company_id, company_code
FROM users
WHERE id = ${id};
`;
if (!user) {
error(404, 'User not found');
}
user.company = {
id: user.company_id
};
delete user.company_id;
return <User>user;
}
export async function getUserWithCompany(id: number): Promise<User> {
const [user] = await sql`
SELECT
u.id,
u.username,
u.perms,
u.email,
u.phone,
u.full_name AS "fullName",
u.created_at AT TIME ZONE 'UTC' AS "createdAt",
u.last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
u.active,
c.id AS company_id,
c.name AS company_name,
c.description AS company_description,
c.website AS company_website,
c.created_at AS company_created_at
FROM
users u
LEFT JOIN
companies c ON u.company_id = c.id
WHERE
u.id = ${id};
`;
if (!user) {
error(404, 'User not found');
}
user.company = {
id: user.company_id,
name: user.company_name,
description: user.company_description,
website: user.company_website,
createdAt: user.company_created_at
};
// Remove the company-specific columns from the user object
delete user.company_id;
delete user.company_name;
delete user.company_description;
delete user.company_website;
delete user.company_created_at;
return <User>user;
}
export async function getUserWithCompanyAndApplications(
id: number
): Promise<{ user: User; applications: Application[] }> {
const data = await sql`
WITH company_data AS (
SELECT
id,
name,
description,
website,
created_at AT TIME ZONE 'UTC' AS "createdAt"
FROM companies
WHERE id = (SELECT company_id FROM users WHERE id = ${id})
),
user_data AS (
SELECT
id,
username,
perms,
email,
phone,
full_name AS "fullName",
created_at AT TIME ZONE 'UTC' AS "createdAt",
last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
active
FROM users
WHERE "id" = ${id}
),
application_data AS (
SELECT
id,
posting_id AS "postingId",
(SELECT title FROM postings WHERE id = posting_id) AS "postingTitle",
created_at AT TIME ZONE 'UTC' AS "createdAt"
FROM applications
WHERE "user_id" = ${id}
)
SELECT
(
SELECT row_to_json(company_data)
FROM company_data
) AS company,
(
SELECT row_to_json(user_data)
FROM user_data
) AS user,
(
SELECT json_agg(row_to_json(application_data))
FROM application_data
) AS applications;
`;
if (!data) {
error(404, 'User not found');
}
let user = data[0].user;
user.company = data[0].company;
user.createdAt = new Date(user.createdAt);
user.lastSignIn = new Date(user.lastSignIn);
if (user.company) {
user.company.createdAt = new Date(user.company.createdAt);
}
let applications = data[0].applications;
if (applications) {
applications.forEach((application: { createdAt: string | number | Date }) => {
application.createdAt = new Date(application.createdAt);
});
}
return {
user: <User>user,
applications: <Application[]>applications
};
}
// should require MANAGE_USERS permission
export async function deleteUser(id: number): Promise<void> {
await sql`
DELETE FROM users
WHERE id = ${id};
`;
}
// should require MANAGE_TAGS permission
export async function getTags(searchQuery: string | null): Promise<Tag[]> {
return sql<Tag[]>`
SELECT id, display_name as "displayName", created_at AT TIME ZONE 'UTC' AS "createdAt"
FROM tags
WHERE display_name ILIKE ${searchQuery ? `%${searchQuery}%` : '%'};
`;
}
export async function updateLastSignin(username: string): Promise<void> {
await sql`
UPDATE users
SET last_signin = NOW()
WHERE username = ${username};
`;
}
export async function createCompany(company: Company): Promise<number> {
const response = await sql`
INSERT INTO companies (name, description, website, created_at, company_code)
VALUES (${company.name}, ${company.description}, ${company.website}, NOW(), generate_company_code(CAST(CURRVAL('companies_id_seq') AS INT)))
RETURNING id;
`;
await saveLogo(company);
return response[0].id;
}
export async function editCompany(company: Company): Promise<number> {
const response = await sql`
UPDATE companies
SET name = ${company.name}, description = ${company.description}, website = ${company.website}
WHERE id = ${company.id}
RETURNING id;
`;
await saveLogo(company);
return response[0].id;
}
export async function deleteCompany(id: number): Promise<void> {
await sql`
DELETE FROM companies
WHERE id = ${id};
`;
await deleteLogo(<Company>{ id: id });
}
export async function getCompany(id: number): Promise<Company> {
const [company] = await sql`
SELECT id, name, description, website, created_at AS "createdAt", company_code AS "companyCode"
FROM companies
WHERE id = ${id};
`;
if (!company) {
error(404, 'Company not found');
}
return <Company>company;
}
export async function getCompanyFullData(
id: number
): Promise<{ company: Company; users: User[]; postings: Posting[] }> {
const data = await sql`
WITH company_data AS (
SELECT
id,
name,
description,
website,
created_at AT TIME ZONE 'UTC' AS "createdAt"
FROM companies
WHERE id = ${id}
),
user_data AS (
SELECT
id,
username,
email,
phone,
full_name AS "fullName"
FROM users
WHERE "company_id" = ${id}
),
posting_data AS (
SELECT
id,
title,
description,
employer_id AS "employerId",
address,
employment_type AS "employmentType",
wage,
link,
tag_ids AS "tagIds",
created_at AT TIME ZONE 'UTC' AS "createdAt",
updated_at AT TIME ZONE 'UTC' AS "updatedAt",
flyer_link AS "flyerLink"
FROM postings
WHERE "company_id" = ${id}
)
SELECT
(
SELECT row_to_json(company_data)
FROM company_data
) AS company,
(
SELECT json_agg(row_to_json(user_data))
FROM user_data
) AS users,
(
SELECT json_agg(row_to_json(posting_data))
FROM posting_data
) AS postings;
`;
if (!data) {
error(404, 'Company not found');
}
if (data[0].company) {
data[0].company.createdAt = new Date(data[0].company.createdAt);
}
if (data[0].postings) {
data[0].postings.forEach(
(posting: {
createdAt: string | number | Date;
updatedAt: string | number | Date;
tagIds: number[] | undefined;
tags: { id: number; displayName: null; createdAt: null }[];
}) => {
posting.createdAt = new Date(posting.createdAt);
posting.updatedAt = new Date(posting.updatedAt);
if (posting.tagIds) {
posting.tagIds?.forEach((tagId: number) => {
posting.tags.push({ id: tagId, displayName: null, createdAt: null });
});
}
delete posting.tagIds;
}
);
}
return {
company: <Company>data[0].company,
users: <User[]>data[0].users,
postings: <Posting[]>data[0].postings
};
}
export async function createPosting(posting: Posting): Promise<number> {
if (posting.tagIds === null || posting.tagIds === undefined) {
posting.tagIds = [];
}
posting.tags?.forEach((tag) => {
posting.tagIds?.push(tag.id);
});
if (posting.companyId === null || posting.companyId === undefined) {
if (posting.company) {
posting.companyId = posting.company.id;
} else {
posting.companyId = null;
}
}
const response = await sql`
INSERT INTO postings (title, description, employer_id, address, employment_type, wage, link, tag_ids, created_at, updated_at, flyer_link, company_id)
VALUES (${posting.title}, ${posting.description}, ${posting.employerId}, ${posting.address}, ${posting.employmentType}, ${posting.wage}, ${posting.link}, ${posting.tagIds}, NOW(), NOW(), ${posting.flyerLink}, ${posting.companyId})
RETURNING id;
`;
return response[0].id;
}
export async function editPosting(posting: Posting): Promise<number> {
if (posting.tagIds === null || posting.tagIds === undefined) {
posting.tagIds = [];
}
posting.tags?.forEach((tag) => {
posting.tagIds?.push(tag.id);
});
if (posting.companyId === null || posting.companyId === undefined) {
if (posting.company) {
posting.companyId = posting.company.id;
} else {
posting.companyId = null;
}
}
const response = await sql`
UPDATE postings
SET title = ${posting.title}, description = ${posting.description}, employer_id = ${posting.employerId}, address = ${posting.address}, employment_type = ${posting.employmentType}, wage = ${posting.wage}, link = ${posting.link}, tag_ids = ${posting.tagIds}, updated_at = NOW(), flyer_link = ${posting.flyerLink}, company_id = ${posting.companyId}
WHERE id = ${posting.id}
RETURNING id;
`;
return response[0].id;
}
export async function deletePosting(id: number): Promise<void> {
await sql`
DELETE FROM postings
WHERE id = ${id};
`;
}
export async function getCompanyEmployers(
id: number
): Promise<{ company: Company; users: User[] }> {
const data = await sql`
WITH company_data AS (
SELECT
id,
name,
description,
website,
created_at AT TIME ZONE 'UTC' AS "createdAt",
company_code AS "companyCode"
FROM companies
WHERE id = ${id}
),
user_data AS (SELECT id,
username,
email,
phone,
full_name AS "fullName",
created_at AT TIME ZONE 'UTC' AS "createdAt",
last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
company_id as "companyId"
FROM users
WHERE "company_id" = ${id})
SELECT
(
SELECT row_to_json(company_data)
FROM company_data
) AS company,
(
SELECT json_agg(row_to_json(user_data))
FROM user_data
) AS users;
`;
if (!data) {
error(404, 'Company not found');
}
if (data[0].users) {
data[0].users.forEach(
(user: {
company: { id: any };
companyId: any;
createdAt: string | number | Date;
lastSignIn: string | number | Date;
}) => {
user.company = {
id: user.companyId
};
user.createdAt = new Date(user.createdAt);
user.lastSignIn = new Date(user.lastSignIn);
delete user.companyId;
}
);
}
return {
company: <Company>data[0].company,
users: <User[]>data[0].users
};
}
export async function getCompanyEmployersAndRequests(
id: number
): Promise<{ company: Company; employers: User[]; requests: User[] }> {
const data = await sql`
WITH company_data AS (
SELECT
id,
name,
description,
website,
created_at AT TIME ZONE 'UTC' AS "createdAt",
company_code AS "companyCode"
FROM companies
WHERE id = ${id}
),
employer_data AS (SELECT id,
username,
email,
phone,
full_name AS "fullName",
created_at AT TIME ZONE 'UTC' AS "createdAt",
last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
company_id as "companyId"
FROM users
WHERE "company_id" = ${id}),
request_data AS (SELECT id,
username,
email,
phone,
full_name AS "fullName",
created_at AT TIME ZONE 'UTC' AS "createdAt",
last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
company_id as "companyId"
FROM users
WHERE "company_code" = (SELECT company_code FROM companies WHERE id = ${id}))
SELECT
(
SELECT row_to_json(company_data)
FROM company_data
) AS company,
(
SELECT json_agg(row_to_json(employer_data))
FROM employer_data
) AS employers,
(
SELECT json_agg(row_to_json(request_data))
FROM request_data
) AS requests;
`;
if (!data) {
error(404, 'Company not found');
}
if (data[0].employers) {
data[0].employers.forEach(
(user: {
company: { id: any };
companyId: any;
createdAt: string | number | Date;
lastSignIn: string | number | Date;
}) => {
user.company = {
id: user.companyId
};
user.createdAt = new Date(user.createdAt);
user.lastSignIn = new Date(user.lastSignIn);
delete user.companyId;
}
);
}
if (data[0].requests) {
data[0].requests.forEach(
(user: {
company: { id: any };
companyId: any;
createdAt: string | number | Date;
lastSignIn: string | number | Date;
}) => {
user.company = {
id: user.companyId
};
user.createdAt = new Date(user.createdAt);
user.lastSignIn = new Date(user.lastSignIn);
delete user.companyId;
}
);
}
return {
company: <Company>data[0].company,
employers: <User[]>data[0].employers,
requests: <User[]>data[0].requests
};
}
export async function removeEmployerFromCompany(companyId: number, userId: number): Promise<void> {
await sql`
UPDATE users
SET company_id = NULL,
company_code = NULL
WHERE id = ${userId};
`;
}
export async function addEmployerToCompany(companyId: number, userId: number): Promise<void> {
await sql`
UPDATE users
SET company_id = ${companyId}
WHERE id = ${userId};
`;
}
export async function getPostings(searchQuery: string | null = null): Promise<Posting[]> {
const postings = await sql<Posting[]>`
SELECT p.id,
p.title,
p.description,
p.employer_id AS "employerId",
p.address,
p.employment_type AS "employmentType",
p.wage,
p.link,
p.tag_ids AS "tagIds",
p.created_at AT TIME ZONE 'UTC' AS "createdAt",
p.updated_at AT TIME ZONE 'UTC' AS "updatedAt",
p.flyer_link AS "flyerLink",
p.company_id AS "companyId",
c.name AS "companyName"
FROM postings p
LEFT JOIN companies c ON p.company_id = c.id
WHERE title ILIKE ${searchQuery ? `%${searchQuery}%` : '%'};
`;
postings.forEach((posting) => {
posting.company = <Company>{};
if (posting.companyName) {
posting.company.name = posting.companyName;
}
delete posting.companyName;
posting.tags = [];
posting.employmentType = EmploymentType[posting.employmentType as keyof typeof EmploymentType];
if (posting.tagIds) {
posting.tagIds?.forEach((tagId: number) => {
posting.tags.push({ id: tagId, displayName: null, createdAt: null });
});
}
delete posting.tagIds;
});
return <Posting[]>(<unknown>postings);
}
export async function getPosting(id: number): Promise<Posting> {
const data = await sql<Posting[]>`
SELECT id,
title,
description,
employer_id AS "employerId",
address,
employment_type AS "employmentType",
wage,
link,
tag_ids AS "tagIds",
created_at AT TIME ZONE 'UTC' AS "createdAt",
updated_at AT TIME ZONE 'UTC' AS "updatedAt",
flyer_link AS "flyerLink",
company_id AS "companyId"
FROM postings
WHERE id = ${id};
`;
const posting = data[0];
posting.tags = [];
posting.employmentType = EmploymentType[posting.employmentType as keyof typeof EmploymentType];
if (posting.tagIds) {
posting.tagIds?.forEach((tagId: number) => {
posting.tags.push({ id: tagId, displayName: null, createdAt: null });
});
}
delete posting.tagIds;
if (posting.createdAt) {
posting.createdAt = new Date(posting.createdAt);
}
if (posting.updatedAt) {
posting.updatedAt = new Date(posting.updatedAt);
}
return posting;
}
export async function getPostingWithCompanyUser(id: number): Promise<Posting> {
const data = await sql`
WITH company_data AS (
SELECT
id,
name,
description,
website,
created_at AS "createdAt"
FROM companies
WHERE id = (SELECT company_id FROM postings WHERE id = ${id})
),
user_data AS (
SELECT
username,
email,
phone,
full_name AS "fullName"
FROM users
WHERE "company_id" = (SELECT company_id FROM postings WHERE id = ${id})
),
posting_data AS (
SELECT
id,
title,
description,
employer_id AS "employerId",
address,
employment_type AS "employmentType",
wage,
link,
tag_ids AS "tagIds",
created_at AT TIME ZONE 'UTC' AS "createdAt",
updated_at AT TIME ZONE 'UTC' AS "updatedAt",
flyer_link AS "flyerLink"
FROM postings
WHERE id = ${id}
)
SELECT
(
SELECT row_to_json(company_data)
FROM company_data
) AS company,
(
SELECT row_to_json(user_data)
FROM user_data
) AS user,
(
SELECT row_to_json(posting_data)
FROM posting_data
) AS posting;
`;
if (!data) {
error(404, 'Posting not found');
}
let posting = <Posting>data[0].posting;
posting.company = <Company>data[0].company;
posting.employer = <User>data[0].user;
if (posting.createdAt) {
posting.createdAt = new Date(posting.createdAt);
}
if (posting.updatedAt) {
posting.updatedAt = new Date(posting.updatedAt);
}
return posting;
}
export async function createApplication(application: Application): Promise<number> {
const response = await sql`
INSERT INTO applications (posting_id, user_id, candidate_statement, created_at)
VALUES (${application.postingId}, ${application.userId}, ${application.candidateStatement}, NOW())
RETURNING id;
`;
sendEmployerNotificationEmail(application.postingId).catch((err) => {
console.error('Failed to send employer notification email: ', err);
});
return response[0].id;
}
export async function deleteApplication(id: number): Promise<void> {
const response = await sql`
DELETE FROM applications
WHERE id = ${id};
`;
}
export async function deleteApplicationWithUser(
applicationId: number,
userId: number
): Promise<void> {
console.log(applicationId, userId);
const response = await sql`
DELETE FROM applications
WHERE id = ${applicationId} AND user_id = ${userId};
`;
}
export async function getApplications(postingId: number): Promise<Application[]> {
const data = await sql`
SELECT
a.id,
a.candidate_statement AS "candidateStatement",
a.created_at AS "createdAt",
u.id AS "userId",
u.username,
u.email,
u.phone,
u.full_name AS "fullName"
FROM applications a
JOIN users u ON a.user_id = u.id
WHERE a.posting_id = ${postingId};
`;
data.forEach((application) => {
application.createdAt = new Date(application.createdAt);
application.user = {
id: application.userId,
username: application.username,
email: application.email,
phone: application.phone,
fullName: application.fullName,
resume: fs.existsSync(
path.join(process.cwd(), 'static', 'uploads', 'resumes', `${application.userId}.pdf`)
)
};
delete application.userId;
delete application.username;
delete application.email;
delete application.phone;
delete application.fullName;
});
return <Application[]>(<unknown>data);
}
export async function setUserCompanyId(userId: number, companyId: number): Promise<void> {
await sql`
UPDATE users
SET company_id = ${companyId},
company_code = (SELECT company_code FROM companies WHERE id = ${companyId})
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
};
}