Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Company watch list (WIP) #393

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
91 changes: 21 additions & 70 deletions backend/src/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
AdminCreateJobRequest,
AdminApprovedJobPostsRequest,
} from './types/request';
import { env } from './environment';
import CompanyFunctions from './company';

const LM = new LogModule('ADMIN');

Expand Down Expand Up @@ -379,7 +379,11 @@ You job post request titled "${jobToReject.role}" has been rejected as it does n
<p>CSESoc Jobs Board Administrator</p>
`,
);
Logger.Info(LM, `Admin ID=${req.adminID} unverified COMPANY=${req.params.companyAccountID}`);
Logger.Info(
LM,
`Admin ID=${req.adminID} unverified COMPANY=${req.params.companyAccountID}`,
);

return {
status: 200,
msg: {
Expand Down Expand Up @@ -447,38 +451,7 @@ You job post request titled "${jobToReject.role}" has been rejected as it does n
res,
async (): Promise<IResponseWithStatus> => {
const { companyID } = req.params;
Logger.Info(LM, `Admin ID=${req.adminID} attempting to find company ID=${companyID}`);
const company = await Helpers.doSuccessfullyOrFail(
async () => AppDataSource.getRepository(Company)
.createQueryBuilder()
.where('Company.id = :id', { id: companyID })
.getOne(),
`Couldn't get request company object ID=${companyID} as Admin ID=${req.adminID}`,
);

// get it's associated company account to verify
company.companyAccount = await Helpers.doSuccessfullyOrFail(
async () => AppDataSource.createQueryBuilder()
.relation(Company, 'companyAccount')
.of(company)
.loadOne(),
`Could not get the related company account for company ID=${company.id}`,
);

company.jobs = await Helpers.doSuccessfullyOrFail(
async () => AppDataSource.createQueryBuilder().relation(Company, 'jobs').of(company).loadMany(),
`Failed to find jobs for COMPANY_ACCOUNT=${companyID}`,
);

// verify whether the associated company account is verified
if (!company.companyAccount.verified) {
throw new Error(
`Admin ID=${req.adminID} attempted to create a job post for company ID=${companyID} however it was not a verified company`,
);
}

// create the job now
// ensure required parameters are present
const msg = {
applicationLink: req.body.applicationLink.trim(),
description: req.body.description.trim(),
Expand All @@ -499,14 +472,21 @@ You job post request titled "${jobToReject.role}" has been rejected as it does n
Helpers.requireParameters(msg.expiry);
Helpers.requireParameters(msg.isPaid);

Helpers.isValidJobMode(msg.jobMode);
Helpers.isValidStudentDemographic(msg.studentDemographic);
Helpers.isValidJobType(msg.jobType);
Helpers.isValidWorkingRights(msg.workingRights);
Helpers.isValidWamRequirement(msg.wamRequirements);
Logger.Info(LM, `Admin ID=${req.adminID} attempting to find company ID=${companyID}`);

const company = await AppDataSource.getRepository(Company)
.createQueryBuilder()
.leftJoinAndSelect('Company.companyAccount', 'companyAccount')
.where('Company.id = :id', { id: companyID })
.andWhere('companyAccount.verified = :verified', { verified: true })
.getOne();

if (!company) {
throw Error(`Could not find COMPANY=${companyID}`);
}

const companyAccountID = company.companyAccount.id;

Helpers.isDateInTheFuture(msg.expiry);
Helpers.validApplicationLink(msg.applicationLink);
Logger.Info(
LM,
`Attempting to create job for COMPANY=${companyID} with ROLE=${msg.role} DESCRIPTION=${msg.description} applicationLink=${msg.applicationLink} as adminID=${req.adminID}`,
Expand All @@ -526,38 +506,9 @@ You job post request titled "${jobToReject.role}" has been rejected as it does n
newJob.wamRequirements = msg.wamRequirements;
// jobs created by admin are implicitly approved
newJob.approved = true;
// mark this job as one that the admin has created
newJob.adminCreated = true;

await MailFunctions.AddMailToQueue(
env.MAIL_USERNAME,
'CSESoc Jobs Board - CSESoc has created a job on your behalf',
`
Congratulations! CSESoc has create a job post on your behalf titled "${newJob.role}". UNSW CSESoc students are now able to view the posting.
<br>
<p>Best regards,</p>
<p>CSESoc Jobs Board Administrator</p>
`,
);

company.jobs.push(newJob);

await AppDataSource.manager.save(company);

const newJobID: number = company.jobs[company.jobs.length - 1].id;
Logger.Info(
LM,
`Created JOB=${newJobID} for COMPANY_ACCOUNT=${companyID} as adminID=${req.adminID}`,
);

// check to see if that job is queryable
await Helpers.doSuccessfullyOrFail(
async () => AppDataSource.getRepository(Job)
.createQueryBuilder()
.where('Job.id = :id', { id: newJobID })
.getOne(),
`Failed to fetch the newly created JOB=${newJobID}`,
);
await CompanyFunctions.createJob(newJob, companyAccountID, true);

return {
status: StatusCodes.OK,
Expand Down
146 changes: 86 additions & 60 deletions backend/src/company.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,23 +203,94 @@ export default class CompanyFunctions {
);
}

public static async CreateJob(
this: void,
req: CreateJobRequest,
res: Response,
next: NextFunction,
) {
public static async createJob(job: Job, companyAccountID: number, adminCreated: boolean) {
const companyAccount = await AppDataSource.getRepository(CompanyAccount)
.createQueryBuilder()
.leftJoinAndSelect('CompanyAccount.company', 'company')
.leftJoinAndSelect('company.jobs', 'job')
.where('CompanyAccount.id = :id', { id: companyAccountID })
.andWhere('CompanyAccount.verified = :verified', { verified: true })
.getOne();

if (!companyAccount) {
throw new Error(`Could not find a verified COMPANY=${companyAccountID} to create a job for`);
}

const { company } = companyAccount;
const companyJobs = company.jobs;

companyJobs.push(job);
await AppDataSource.manager.save(companyAccount);

const newJobID = companyJobs.at(-1).id;
const newJob = await AppDataSource.getRepository(Job)
.createQueryBuilder()
.where('Job.id = :id', { id: newJobID })
.getOne();

if (!newJob) {
throw new Error(`Failed to create a new job for COMPANY=${companyAccountID}`);
}

if (adminCreated) {
await MailFunctions.AddMailToQueue(
companyAccount.username,
'CSESoc Jobs Board - CSESoc has created a job on your behalf',
`
Congratulations! CSESoc has create a job post on your behalf titled "${newJob.role}". UNSW CSESoc students are now able to view the posting.
<br>
<p>Best regards,</p>
<p>CSESoc Jobs Board Administrator</p>
`,
);
} else {
await MailFunctions.AddMailToQueue(
companyAccount.username,
'CSESoc Jobsboard - Job Post request submitted',
`
Thank you for adding a job post to the CSESoc Jobs Board. As part of our aim to ensure student safety, we check all job posting requests to ensure they follow our guidelines, as the safety of our students is our utmost priority.
<br>
A result will be sent to you shortly.
<br>
<p>Best regards,</p>
<p>CSESoc Jobsboard</p>
`,
);
}

const subscribers = company.studentSubscribers;
await Promise.all(
subscribers.map(async (zID) => {
await MailFunctions.AddMailToQueue(
Helpers.getEmailFromZID(zID),
`CSESoc Jobsboard - Job opening from ${company.name}`,
`
Hi,

This is a email to let you kow that ${company.name} to has opened a new position (${job.role}).
Visit https://jobsboard.csesoc.unsw.edu.au to check it out!
<br>

If you no longer wish to receive updates from ${company.name}, please update your subscriptions preferences on Jobsboard.
<br>
<p>Best regards, </p>
<p>CSESoc Jobsboard</p>
`,
);
}),
);

Logger.Info(LM, `New JOB=${newJobID} successfully created for COMPANY=${companyAccountID}`);

return newJobID;
}

public static async CreateJob(req: CreateJobRequest, res: Response, next: NextFunction) {
await Helpers.catchAndLogError(
res,
async (): Promise<IResponseWithStatus> => {
if (req.companyAccountID === undefined) {
return {
status: StatusCodes.UNAUTHORIZED,
msg: { token: req.newJbToken },
};
}
// ensure required parameters are present
const msg = {
companyAccountID: parseInt(req.companyAccountID, 10),
applicationLink: req.body.applicationLink.trim(),
description: req.body.description.trim(),
role: req.body.role.trim(),
Expand All @@ -233,18 +304,15 @@ export default class CompanyFunctions {
isPaid: req.body.isPaid,
};

// ? double check data sent from frontend are guaranteed valid before removing
Helpers.requireParameters(msg.role);
Helpers.requireParameters(msg.description);
Helpers.requireParameters(msg.applicationLink);
Helpers.requireParameters(msg.expiry);
Helpers.requireParameters(msg.isPaid);
Helpers.isDateInTheFuture(msg.expiry);
Helpers.validApplicationLink(msg.applicationLink);

Logger.Info(
LM,
`Attempting to create job for COMPANY=${req.companyAccountID} with ROLE=${msg.role} DESCRIPTION=${msg.description} applicationLink=${msg.applicationLink}`,
`Attempting to create job for COMPANY=${msg.companyAccountID} with ROLE=${msg.role} DESCRIPTION=${msg.description} applicationLink=${msg.applicationLink}`,
);

const newJob = new Job();
Expand All @@ -260,50 +328,8 @@ export default class CompanyFunctions {
newJob.additionalInfo = msg.additionalInfo;
newJob.wamRequirements = msg.wamRequirements;

// get the company and the list of its jobs
const companyAccount: CompanyAccount = await AppDataSource.getRepository(CompanyAccount)
.createQueryBuilder()
.leftJoinAndSelect('CompanyAccount.company', 'company')
.leftJoinAndSelect('company.jobs', 'job')
.where('CompanyAccount.id = :id', { id: req.companyAccountID })
.andWhere('CompanyAccount.verified = :verified', { verified: true })
.getOne();

// prevent job from being posted since the provided company account is not verified
if (companyAccount === null) {
return {
status: StatusCodes.FORBIDDEN,
msg: { token: req.newJbToken },
};
}

// add the new job to the list and commit to db
companyAccount.company.jobs.push(newJob);
await AppDataSource.manager.save(companyAccount);

// get the supposed id for the new job and check if it's queryable from the db
const newJobID = companyAccount.company.jobs[companyAccount.company.jobs.length - 1].id;
const newJobID = await this.createJob(newJob, msg.companyAccountID, false);

Logger.Info(LM, `Created JOB=${newJobID} for COMPANY_ACCOUNT=${req.companyAccountID}`);

await AppDataSource.getRepository(Job)
.createQueryBuilder()
.where('Job.id = :id', { id: newJobID })
.getOne();

await MailFunctions.AddMailToQueue(
companyAccount.username,
'CSESoc Jobs Board - Job Post request submitted',
`
Thank you for adding a job post to the CSESoc Jobs Board. As part of our aim to ensure student safety, we check all job posting requests to ensure they follow our guidelines, as the safety of our students is our utmost priority.
<br>
A result will be sent to you shortly.
<br>
<p>Best regards,</p>
<p>Adam Tizzone</p>
<p>CSESoc Jobs Board Administrator</p>
`,
);
return {
status: StatusCodes.OK,
msg: { token: req.newJbToken, id: newJobID },
Expand Down
10 changes: 10 additions & 0 deletions backend/src/entity/company.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import {
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';

import type Job from './job';
import type CompanyAccount from './company_account';

type ZID = string;

@Entity()
export default class Company {
@PrimaryGeneratedColumn()
Expand Down Expand Up @@ -51,6 +54,13 @@ export default class Company {
@OneToOne('CompanyAccount', (companyAccount: CompanyAccount) => companyAccount.company)
public companyAccount: CompanyAccount;

@Column({
type: 'text',
array: true,
default: [],
})
public studentSubscribers: ZID[];

@CreateDateColumn()
createdAt: Date;

Expand Down
5 changes: 5 additions & 0 deletions backend/src/entity/student_profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
} from 'typeorm';
import { WamRequirements, WorkingRights } from '../types/job-field';

type CompanyID = number;

@Entity()
export default class StudentProfile {
@PrimaryGeneratedColumn()
Expand All @@ -29,6 +31,9 @@ export default class StudentProfile {
})
public workingRights: WorkingRights;

@Column({ type: 'int', array: true, default: [] })
public subscribedCompanies: CompanyID[];

@CreateDateColumn()
createdAt: Date;

Expand Down
Loading