working #18
@@ -5,42 +5,42 @@ Server:
|
||||
When a company is created:
|
||||
Send email -> verify ownership of the email
|
||||
|
||||
Need to timeout email reset tokens:
|
||||
Resume:
|
||||
Block API Access as much as possible [ Disallow AI keyword filters ]
|
||||
|
||||
Auth:
|
||||
Make sure autorenew works
|
||||
|
||||
When Job Posting Closes Successful:
|
||||
Update the company rating
|
||||
|
||||
JobCleanupService:
|
||||
Need to update notification email
|
||||
|
||||
Client:
|
||||
jobs/new:
|
||||
Want to add Required skills to help with filtering
|
||||
When enter is pressed it tries to submit the form
|
||||
Should run the whole carosel on enter before the submit is sent
|
||||
Need to validate input before allowing next step
|
||||
Job Listing Skills exists but isn't implimented in the UI
|
||||
Tab doesnt do anything
|
||||
Want to add completed job listing preview at end of carosel
|
||||
Edit employees not implimented yet
|
||||
|
||||
Jobs/editor:
|
||||
Jobs/editor w/ Querystring JobID=# is not implimented yet
|
||||
Edit employees not implimented yet
|
||||
|
||||
Resume:
|
||||
Resume builder minimal user input [ Dont allow AI input ]
|
||||
Allow company to look up users if their resume is public [ Maybe auto with notify ]
|
||||
Allow multiple resume's for job specific work
|
||||
Create advanced filtering tools for company lookup and resume lookup
|
||||
|
||||
Job Board:
|
||||
Allow users to look up jobs and apply [ Boost visibility | Completely manual ]
|
||||
Mark ghost listings to allow users to be informed and put companies on blast
|
||||
|
||||
CompanyConnect:
|
||||
need to lookup company before making a new one
|
||||
|
||||
|
||||
database:
|
||||
Add Applied Jobs Table
|
||||
|
||||
|
||||
|
||||
Task:
|
||||
Block API Access as much as possible [ Rate limit | Auth Req | CORS | Disallow AI keyword filters ]
|
||||
Resume builder minimal user input [ Dont allow AI input ]
|
||||
Auto unlist jobs after a month of no activity [ Multiple offenders marked ]
|
||||
Dont allow external applications for users on company sites from the start
|
||||
Allow company to look up users if their resume is public [ Maybe auto with notify ]
|
||||
Allow users to look up jobs and apply [ Boost visibility | Completely manual ]
|
||||
Allow multiple resume's for job specific work
|
||||
Mark ghost listings to allow users to be informed and put companies on blast
|
||||
Create advanced filtering tools for company lookup and resume lookup
|
||||
|
||||
Create and Auth Database based on the docker compose
|
||||
Create a server table inside the auth database
|
||||
Point all requests after auth to the correct regional server. -> Currently only Mistox-West exists
|
||||
|
||||
CompanyConnect | need to lookup company before making a new one
|
||||
|
||||
Finish Auth setup
|
||||
Make sure autorenew works
|
||||
|
||||
Jobs/editor w/ Querystring JobID=# is not implimented yet
|
||||
Company -> Edit employees not implimented yet
|
||||
|
||||
Resume fields in angular models need to be public
|
||||
+31
-2
@@ -131,6 +131,8 @@ CREATE TABLE IF NOT EXISTS `Company` (
|
||||
`EmailVerified` boolean DEFAULT 0,
|
||||
`WebsiteURL` varchar(255) DEFAULT NULL,
|
||||
`Logo` mediumblob DEFAULT NULL,
|
||||
`JobsClosedSuccessful` int DEFAULT 0,
|
||||
`JobsAutoClosed` int DEFAULT 0,
|
||||
`Phone` varchar(20) DEFAULT NULL,
|
||||
`PostalCode` varchar(20) NOT NULL,
|
||||
`Country` char(2) NOT NULL,
|
||||
@@ -161,9 +163,36 @@ CREATE TABLE IF NOT EXISTS `JobListing` (
|
||||
`JobType` varchar(20) NOT NULL,
|
||||
`Remote` boolean DEFAULT 0,
|
||||
`Description` text NOT NULL,
|
||||
`CreatedTime` datetime Default NULL,
|
||||
`CreatedTime` datetime DEFAULT NULL,
|
||||
`ModifiedTime` datetime DEFAULT NULL,
|
||||
`IsDeleted` boolean Default 0,
|
||||
`IsDeleted` boolean DEFAULT 0,
|
||||
PRIMARY KEY (`ID`),
|
||||
FOREIGN KEY (`CompanyID`) REFERENCES `Company`(`ID`) ON DELETE CASCADE
|
||||
) AUTO_INCREMENT=1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `JobListingSkill` (
|
||||
`ID` int NOT NULL AUTO_INCREMENT,
|
||||
`JobListingID` int NOT NULL,
|
||||
`Name` varchar(150) NOT NULL,
|
||||
`Description` text DEFAULT NULL,
|
||||
PRIMARY KEY (`ID`),
|
||||
FOREIGN KEY (`JobListingID`) REFERENCES `JobListing`(`ID`) ON DELETE CASCADE
|
||||
) AUTO_INCREMENT=1;
|
||||
|
||||
-- Application Section
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `JobApplication` (
|
||||
`ID` int NOT NULL AUTO_INCREMENT,
|
||||
`AccountID` int NOT NULL,
|
||||
`ResumeID` int NOT NULL,
|
||||
`JobListingID` int NOT NULL,
|
||||
`ResponseEmail` varchar(255) DEFAULT NULL,
|
||||
`DateApplied` datetime DEFAULT NULL,
|
||||
`ResponseStatus` varchar(50) NOT NULL DEFAULT 'Pending',
|
||||
`HasBeenViewed` boolean DEFAULT 0,
|
||||
`Rating` int DEFAULT NULL,
|
||||
`Notes` text DEFAULT NULL,
|
||||
PRIMARY KEY (`ID`),
|
||||
FOREIGN KEY (`ResumeID`) REFERENCES `Resume`(`ID`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`JobListingID`) REFERENCES `JobListing`(`ID`) ON DELETE CASCADE
|
||||
) AUTO_INCREMENT=1;
|
||||
@@ -0,0 +1,12 @@
|
||||
export class Application {
|
||||
public id: number | null = null;
|
||||
public accountID: number = 0;
|
||||
public resumeID: number = 0;
|
||||
public jobListingID: number = 0;
|
||||
public responseEmail: string = "";
|
||||
public dateApplied: Date = new Date();
|
||||
public responseStatus: string = "";
|
||||
public hasBeenViewed: boolean = false;
|
||||
public rating: number = 0;
|
||||
public notes: string = "";
|
||||
}
|
||||
@@ -5,6 +5,8 @@ export class Company {
|
||||
public emailVerified: boolean = false;
|
||||
public websiteURL: string = "";
|
||||
public logo: string = "";
|
||||
public jobsClosedSuccessful: number = 0;
|
||||
public jobsAutoClosed: number = 0;
|
||||
public phone: string = "";
|
||||
public postalCode: string = "";
|
||||
public country: string = ""; // 2 Letter Country Code
|
||||
|
||||
@@ -11,7 +11,15 @@ export class JobListing {
|
||||
public jobType: string = "";
|
||||
public remote: boolean = false;
|
||||
public description: string = "";
|
||||
public skills: JobListingSkills[] = [];
|
||||
public createdTime: Date = new Date();
|
||||
public modifiedTime: Date = new Date();
|
||||
public isDeleted: boolean = false;
|
||||
}
|
||||
|
||||
export class JobListingSkills {
|
||||
public id: number | null = null;
|
||||
public jobListingID: number = 0;
|
||||
public name: string = "";
|
||||
public Description: string = "";
|
||||
}
|
||||
@@ -21,86 +21,86 @@ export class Resume {
|
||||
|
||||
export class ResumeExperience {
|
||||
public id: number | null = null;
|
||||
resumeID: number = 0;
|
||||
jobTitle: string = "";
|
||||
company: string = "";
|
||||
postalCode: string = "";
|
||||
country: string = "";
|
||||
stateOrRegion: string = "";
|
||||
city: string = "";
|
||||
dateStarted: Date = new Date();
|
||||
stillEmployed: boolean = false;
|
||||
dateEnded: Date = new Date();
|
||||
experienceBullets: ResumeExperienceBullet[] = [];
|
||||
public resumeID: number = 0;
|
||||
public jobTitle: string = "";
|
||||
public company: string = "";
|
||||
public postalCode: string = "";
|
||||
public country: string = "";
|
||||
public stateOrRegion: string = "";
|
||||
public city: string = "";
|
||||
public dateStarted: Date = new Date();
|
||||
public stillEmployed: boolean = false;
|
||||
public dateEnded: Date = new Date();
|
||||
public experienceBullets: ResumeExperienceBullet[] = [];
|
||||
}
|
||||
|
||||
export class ResumeExperienceBullet {
|
||||
public id: number | null = null;
|
||||
resumeID: number = 0;
|
||||
resumeExperienceID: number = 0;
|
||||
jobFunction: string = "";
|
||||
public resumeID: number = 0;
|
||||
public resumeExperienceID: number = 0;
|
||||
public jobFunction: string = "";
|
||||
}
|
||||
|
||||
export class ResumeMilitary {
|
||||
public id: number | null = null;
|
||||
resumeID: number = 0;
|
||||
country: string = "";
|
||||
rank: string = "";
|
||||
dateStarted: Date = new Date();
|
||||
stillServing: boolean = false;
|
||||
dateEnded: Date = new Date();
|
||||
millitaryBullets: ResumeMilitaryBullet[] = [];
|
||||
public resumeID: number = 0;
|
||||
public country: string = "";
|
||||
public rank: string = "";
|
||||
public dateStarted: Date = new Date();
|
||||
public stillServing: boolean = false;
|
||||
public dateEnded: Date = new Date();
|
||||
public millitaryBullets: ResumeMilitaryBullet[] = [];
|
||||
}
|
||||
|
||||
export class ResumeMilitaryBullet {
|
||||
public id: number | null = null;
|
||||
resumeID: number = 0;
|
||||
resumeMilitaryID: number = 0;
|
||||
achievement: string = "";
|
||||
description: string = "";
|
||||
public resumeID: number = 0;
|
||||
public resumeMilitaryID: number = 0;
|
||||
public achievement: string = "";
|
||||
public description: string = "";
|
||||
}
|
||||
|
||||
export class ResumeEducation {
|
||||
public id: number | null = null;
|
||||
resumeID: number = 0;
|
||||
degreeType: string = "";
|
||||
degreeField: string = "";
|
||||
school: string = "";
|
||||
postalCode: string = "";
|
||||
country: string = "";
|
||||
stateOrRegion: string = "";
|
||||
city: string = "";
|
||||
dateStarted: Date = new Date();
|
||||
stillStudying: boolean = false;
|
||||
dateEnded: Date = new Date();
|
||||
public resumeID: number = 0;
|
||||
public degreeType: string = "";
|
||||
public degreeField: string = "";
|
||||
public school: string = "";
|
||||
public postalCode: string = "";
|
||||
public country: string = "";
|
||||
public stateOrRegion: string = "";
|
||||
public city: string = "";
|
||||
public dateStarted: Date = new Date();
|
||||
public stillStudying: boolean = false;
|
||||
public dateEnded: Date = new Date();
|
||||
}
|
||||
|
||||
export class ResumeSkill {
|
||||
public id: number | null = null;
|
||||
resumeID: number = 0;
|
||||
name: string = "";
|
||||
description: string = "";
|
||||
public resumeID: number = 0;
|
||||
public name: string = "";
|
||||
public description: string = "";
|
||||
}
|
||||
|
||||
export class ResumeLanguage {
|
||||
public id: number | null = null;
|
||||
resumeID: number = 0;
|
||||
language: string = "";
|
||||
proficiency: string = "";
|
||||
public resumeID: number = 0;
|
||||
public language: string = "";
|
||||
public proficiency: string = "";
|
||||
}
|
||||
|
||||
export class ResumeCertification {
|
||||
public id: number | null = null;
|
||||
resumeID: number = 0;
|
||||
name: string = "";
|
||||
verificationURL: string = "";
|
||||
description: string = "";
|
||||
public resumeID: number = 0;
|
||||
public name: string = "";
|
||||
public verificationURL: string = "";
|
||||
public description: string = "";
|
||||
}
|
||||
|
||||
export class ResumeProject {
|
||||
public id: number | null = null;
|
||||
resumeID: number = 0;
|
||||
name: string = "";
|
||||
url: string = "";
|
||||
description: string = "";
|
||||
public resumeID: number = 0;
|
||||
public name: string = "";
|
||||
public url: string = "";
|
||||
public description: string = "";
|
||||
}
|
||||
@@ -30,4 +30,15 @@ button {
|
||||
margin: 10px;
|
||||
overflow: scroll;
|
||||
padding: 10px;
|
||||
color: var(--Mistox-White);
|
||||
}
|
||||
|
||||
.center-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.center-item img {
|
||||
width: 300px;
|
||||
}
|
||||
@@ -4,11 +4,16 @@
|
||||
</div>
|
||||
<div class="content-frame">
|
||||
<div *ngIf="Comp != null">
|
||||
<h1>{{ Comp.name }}</h1>
|
||||
<h1>{{ Comp.email }}</h1>
|
||||
<div class="center-item">
|
||||
<div><a [href]="'mailto:' + Comp.email" >{{ Comp.email }}</a></div>
|
||||
<div><h1>{{ Comp.name }}</h1></div>
|
||||
<div><a [href]="Comp.websiteURL">{{ Comp.websiteURL }}</a></div>
|
||||
</div>
|
||||
<div class="center-item">
|
||||
<img [src]="Comp.logo" />
|
||||
</div>
|
||||
<h1>{{ Comp.emailVerified }}</h1>
|
||||
<h1>{{ Comp.websiteURL }}</h1>
|
||||
<h1>{{ Comp.logo }}</h1>
|
||||
|
||||
<h1>{{ Comp.phone }}</h1>
|
||||
<h1>{{ Comp.postalCode }}</h1>
|
||||
<h1>{{ Comp.country }}</h1>
|
||||
|
||||
@@ -3,6 +3,19 @@
|
||||
width: calc(100% - 400px);
|
||||
}
|
||||
|
||||
.center-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.center-text h1 {
|
||||
font-size: 100px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.center-text h2 {
|
||||
margin-bottom: 200px;
|
||||
}
|
||||
|
||||
.content-frame {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -16,6 +29,7 @@
|
||||
|
||||
.floating-frame {
|
||||
width: 450px;
|
||||
height: 200px;
|
||||
background-color: var(--Mistox-Frame);
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
@@ -41,8 +55,11 @@ hr {
|
||||
}
|
||||
|
||||
.solution-frame {
|
||||
height: 200px;
|
||||
background-color: blueviolet;
|
||||
width: calc(100% - 515px);
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-bottom: solid 1px #000;
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
<div class="center-frame">
|
||||
<div class="content-frame">
|
||||
|
||||
<div class="center-frame center-text border">
|
||||
<h1>Bored Careers</h1>
|
||||
<h2>The Anti-AI Job Board</h2>
|
||||
</div>
|
||||
|
||||
<div class="content-frame border">
|
||||
<div class="floating-frame">
|
||||
<div class="title-block">
|
||||
<h1>death by a thousand applicants</h1>
|
||||
@@ -11,11 +17,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="solution-frame">
|
||||
|
||||
<div>
|
||||
<p><strong>Rate Limiting - </strong>We use strong rate limiting to prevent bot's from flooding applications</p>
|
||||
<p><strong>Strong Authentication - </strong>All API's require authentication and will ban on suspision of bots</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-frame">
|
||||
<div class="content-frame border">
|
||||
<div class="floating-frame">
|
||||
<div class="title-block">
|
||||
<h1>keyword frenzie</h1>
|
||||
@@ -27,11 +36,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="solution-frame">
|
||||
|
||||
<p><strong>No External Access - </strong>All companies and clients are required to interface with the application. Minimizing the surface for AI resume filters.</p>
|
||||
<p><strong>Skill Based Resumes - </strong>Resume's are skill based not work history based. This allows companies to know what your good at without infering it.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-frame">
|
||||
<div class="content-frame border">
|
||||
<div class="floating-frame">
|
||||
<div class="title-block">
|
||||
<h1>response black-hole</h1>
|
||||
@@ -43,11 +53,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="solution-frame">
|
||||
|
||||
<p><strong>Automated Email Notifications - </strong>No longer will you be left in the dark. No matter how the job listing is closed you will be notified</p>
|
||||
<p><strong>More Analytics - </strong>Visibility into if the company has looked at your resume, view your resume score</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-frame">
|
||||
<div class="content-frame border">
|
||||
<div class="floating-frame">
|
||||
<div class="title-block">
|
||||
<h1>zombie postings</h1>
|
||||
@@ -59,7 +70,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="solution-frame">
|
||||
|
||||
<p><strong>Job Postings Auto Close - </strong>Job postings auto close 30 days after originally posted. If the company creates a repost habit they will be flagged.</p>
|
||||
<p><strong>Collective Flagging - </strong>Companies hold a reputation on the patform and its visible on all their job postings</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +87,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="solution-frame">
|
||||
|
||||
<p><strong>Resume Builder - </strong>No staring at a blank sheet trying to build a resume. Use our curated resume builder</p>
|
||||
<p><strong>Companies Search Tools - </strong>Companies can find you with ease with the strong search tools</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5,7 +5,7 @@
|
||||
--Mistox-Medium: #890620;
|
||||
--Mistox-Light: #B6465F;
|
||||
--Mistox-Bright: #FC440F;
|
||||
--Mistox-Frame: #FF5A00CC;
|
||||
--Mistox-Frame: #FFE0A5;
|
||||
--Mistox-Button: #ff9999;
|
||||
--Mistox-Button-Hover: #ff999977;
|
||||
--Mistox-White: #FFF;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using BoredCareers.Services.DatabaseService;
|
||||
using BoredCareers.Entities;
|
||||
using System.Web.Http;
|
||||
|
||||
namespace BoredCareers.Controllers {
|
||||
[ApiController]
|
||||
[Route("api/application")]
|
||||
public class ApplicationController : MistoxControllerBase {
|
||||
|
||||
public ApplicationController(DatabaseService db) : base(db) {}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetApplication(int ApplicationID) {
|
||||
if (isLoggedIn()) {
|
||||
Application? application = await _databaseService.GetApplication(ApplicationID);
|
||||
if (application != null) {
|
||||
return Ok(application);
|
||||
}
|
||||
return NotFound("Application doesn't exist");
|
||||
}
|
||||
return NotFound("Not logged in");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SetApplication([FromBody] Application application) {
|
||||
if (isLoggedIn()) {
|
||||
if (application.AccountID == getLoggedInUserID()) {
|
||||
await _databaseService.SetApplication(application);
|
||||
return Ok();
|
||||
}
|
||||
return NotFound("Cannot apply for someone else");
|
||||
}
|
||||
return NotFound("Not logged in");
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> DeleteApplication(int ApplicationID) {
|
||||
if (isLoggedIn()) {
|
||||
Application? app = await _databaseService.GetApplication(ApplicationID);
|
||||
if (app != null) {
|
||||
if (app.AccountID == getLoggedInUserID()) {
|
||||
await _databaseService.DeleteApplication(ApplicationID);
|
||||
return Ok();
|
||||
}
|
||||
return NotFound("You cannot delete an app that isnt yours");
|
||||
}
|
||||
return NotFound("Application doesn't exist");
|
||||
}
|
||||
return NotFound("Not logged in");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace BoredCareers.Entities {
|
||||
public class Application {
|
||||
public int? ID { get; set; } // PK
|
||||
public int AccountID { get; set; } // FK
|
||||
public int ResumeID { get; set; } // FK
|
||||
public int JobListingID { get; set; } // FK
|
||||
public string ResponseEmail { get; set; } = "";
|
||||
public DateTime DateApplied { get; set; }
|
||||
public string ResponseStatus { get; set; } = "";
|
||||
public bool HasBeenViewed { get; set; } = false;
|
||||
public int Rating { get; set; }
|
||||
public string Notes { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ namespace BoredCareers.Entities {
|
||||
public bool EmailVerified { get; set; } = false;
|
||||
public string WebsiteURL { get; set; } = "";
|
||||
public string Logo { get; set; } = "";
|
||||
public int JobsClosedSuccessful { get; set; }
|
||||
public int JobsAutoClosed { get; set; }
|
||||
public string Phone { get; set; } = "";
|
||||
public string PostalCode { get; set; } = "";
|
||||
public string Country { get; set; } = ""; // 2 Letter Country Code
|
||||
|
||||
@@ -13,9 +13,17 @@ namespace BoredCareers.Entities {
|
||||
public string JobType { get; set; } = "";
|
||||
public bool Remote { get; set; } = false;
|
||||
public string Description { get; set; } = "";
|
||||
public JobListingSkill[] Skills { get; set; } = [];
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public DateTime ModifiedTime { get; set; }
|
||||
public bool IsDeleted { get; set; } = false;
|
||||
}
|
||||
|
||||
public class JobListingSkill {
|
||||
public int? ID { get; set; } // PK
|
||||
public int JobListingID { get; set; } // FK
|
||||
public string Name { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
}
|
||||
|
||||
}
|
||||
+35
-11
@@ -8,11 +8,11 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
using BoredCareers.Services.TimerService;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Disable null warnings becuse string.IsNullOrEmpty checks for NULL or Empty
|
||||
#pragma warning disable CS8600
|
||||
#pragma warning disable CS8604
|
||||
|
||||
////////////////////////////////
|
||||
@@ -36,8 +36,9 @@ string? _dbpass = Environment.GetEnvironmentVariable("MySQLPass");
|
||||
string dbPass = !string.IsNullOrEmpty(_dbpass) ? _dbpass : "oasv34$8gpv023dd";
|
||||
|
||||
// Create the database serivice
|
||||
DatabaseService databaseService = new DatabaseService(connectionString: "server=" + dbserver + ";user=" + dbUser + ";database=" + dbdatabase + ";password=" + dbPass + ";port=3306;");
|
||||
builder.Services.Add( new ServiceDescriptor( typeof( DatabaseService ), databaseService ) );
|
||||
builder.Services.AddSingleton<DatabaseService>(sp =>
|
||||
new DatabaseService("server=" + dbserver + ";user=" + dbUser + ";database=" + dbdatabase + ";password=" + dbPass + ";port=3306;")
|
||||
);
|
||||
|
||||
////////////////////////////////
|
||||
///////// Email Service ////////
|
||||
@@ -135,15 +136,28 @@ builder.Services.AddAuthentication(options => {
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddCors(o => o.AddDefaultPolicy(builder => {
|
||||
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); // No CORS
|
||||
}));
|
||||
////////////////////////////////
|
||||
/// Rate Limiting Service ////
|
||||
////////////////////////////////
|
||||
|
||||
List<string> allowedOrigins = new List<string>{ "https://boredcareers.com", "https://www.boredcareers.com" };
|
||||
if (builder.Environment.IsDevelopment()) {
|
||||
allowedOrigins.Add("http://localhost:5000");
|
||||
}
|
||||
|
||||
builder.Services.AddCors(options => {
|
||||
options.AddDefaultPolicy(policy => {
|
||||
policy.WithOrigins(allowedOrigins.ToArray())
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddRateLimiter(options => {
|
||||
options.AddPolicy("PerUserPolicy", httpContext => {
|
||||
var userId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
||||
?? httpContext.User.Identity?.Name
|
||||
?? httpContext.Connection.RemoteIpAddress?.ToString();
|
||||
?? $"ip:{httpContext.Connection.RemoteIpAddress}";
|
||||
|
||||
return RateLimitPartition.GetTokenBucketLimiter(userId, key => new TokenBucketRateLimiterOptions {
|
||||
TokenLimit = 10, // max 10 requests
|
||||
@@ -156,9 +170,17 @@ builder.Services.AddRateLimiter(options => {
|
||||
});
|
||||
});
|
||||
|
||||
// Pages Service
|
||||
////////////////////////////////
|
||||
///// Background Services /////
|
||||
////////////////////////////////
|
||||
|
||||
builder.Services.AddHostedService<JobCleanupService>();
|
||||
|
||||
////////////////////////////////
|
||||
///// ASPNET Core Function /////
|
||||
////////////////////////////////
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddRazorPages();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -170,12 +192,14 @@ if( !app.Environment.IsDevelopment() ) {
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRateLimiter();
|
||||
|
||||
app.UseCors();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.MapControllers().RequireRateLimiting("perUserPolicy");
|
||||
app.MapControllers();
|
||||
|
||||
app.MapFallbackToFile("index.html");
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using BoredCareers.Entities;
|
||||
|
||||
namespace BoredCareers.Services.TimerService {
|
||||
public class JobCleanupService : BackgroundService {
|
||||
|
||||
private readonly DatabaseService.DatabaseService _db;
|
||||
private readonly EmailService _em;
|
||||
|
||||
public JobCleanupService(DatabaseService.DatabaseService db, EmailService em) {
|
||||
_db = db;
|
||||
_em = em;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
|
||||
while (!stoppingToken.IsCancellationRequested) {
|
||||
try {
|
||||
// Get listing's past expire
|
||||
JobListingDTO[] deletedJobListings = await _db.GetJobListingsPastExipre();
|
||||
|
||||
// Group them by CompanyID, ListingCount
|
||||
Dictionary<int, int> listingsByCompany = deletedJobListings
|
||||
.GroupBy(l => l.CompanyID)
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
// Update each company's rating
|
||||
foreach (KeyValuePair<int, int> kvp in listingsByCompany) {
|
||||
Company? comp = await _db.GetCompany(kvp.Key);
|
||||
if (comp != null) {
|
||||
comp.JobsAutoClosed += kvp.Value;
|
||||
await _db.SetCompany(comp);
|
||||
}
|
||||
}
|
||||
|
||||
// Get each listing
|
||||
foreach (JobListingDTO listing in deletedJobListings) {
|
||||
// Get each Person
|
||||
string[] emails = await _db.GetApplicationResponseEmailFromJobListing(listing.JobListingID);
|
||||
foreach (string email in emails) {
|
||||
// Send Notify Email
|
||||
_em.Send(email, EmailService.JobAutoClosedSubject, EmailService.JobAutoClosedEmail);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Listing's past expire
|
||||
await _db.DeleteJobListingsPastExipre();
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine($"Error: {e.Message}");
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromHours(2), stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class JobListingDTO {
|
||||
public int JobListingID { get; set; }
|
||||
public int CompanyID { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
using BoredCareers.Entities;
|
||||
using MySql.Data.MySqlClient;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace BoredCareers.Services.DatabaseService {
|
||||
public partial class DatabaseService {
|
||||
|
||||
public async Task<Application[]> GetApplcationsFromAccount(int AccountID) {
|
||||
List<Application> applications = new List<Application>();
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM JobApplication
|
||||
WHERE AccountID = @AccountID
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@AccountID", AccountID);
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _accountid = reader.GetInt32("AccountID");
|
||||
int _resumeid = reader.GetInt32("ResumeID");
|
||||
int _joblistingid = reader.GetInt32("JobListingID");
|
||||
string _responseemail = reader.GetString("ResponseEmail");
|
||||
DateTime _dateapplied = reader.GetDateTime("DateApplied");
|
||||
string _responsestatus = reader.GetString("ResponseStatus");
|
||||
bool _hasbeenviewed = reader.GetBoolean("HasBeenViewed");
|
||||
int _rating = reader.GetInt32("Rating");
|
||||
string _notes = reader.GetString("Notes");
|
||||
|
||||
applications.Add(new Application() {
|
||||
ID = _id,
|
||||
AccountID = _accountid,
|
||||
ResumeID = _resumeid,
|
||||
JobListingID = _joblistingid,
|
||||
ResponseEmail = _responseemail,
|
||||
DateApplied = _dateapplied,
|
||||
ResponseStatus = _responsestatus,
|
||||
HasBeenViewed = _hasbeenviewed,
|
||||
Rating = _rating,
|
||||
Notes = _notes
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return applications.ToArray();
|
||||
}
|
||||
|
||||
public async Task<Application[]> GetApplicationsFromJobListing(int JobListingID) {
|
||||
List<Application> applications = new List<Application>();
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM JobApplication
|
||||
WHERE JobListingID = @JobListingID
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@JobListingID", JobListingID);
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _accountid = reader.GetInt32("AccountID");
|
||||
int _resumeid = reader.GetInt32("ResumeID");
|
||||
int _joblistingid = reader.GetInt32("JobListingID");
|
||||
string _responseemail = reader.GetString("ResponseEmail");
|
||||
DateTime _dateapplied = reader.GetDateTime("DateApplied");
|
||||
string _responsestatus = reader.GetString("ResponseStatus");
|
||||
bool _hasbeenviewed = reader.GetBoolean("HasBeenViewed");
|
||||
int _rating = reader.GetInt32("Rating");
|
||||
string _notes = reader.GetString("Notes");
|
||||
|
||||
applications.Add(new Application() {
|
||||
ID = _id,
|
||||
AccountID = _accountid,
|
||||
ResumeID = _resumeid,
|
||||
JobListingID = _joblistingid,
|
||||
ResponseEmail = _responseemail,
|
||||
DateApplied = _dateapplied,
|
||||
ResponseStatus = _responsestatus,
|
||||
HasBeenViewed = _hasbeenviewed,
|
||||
Rating = _rating,
|
||||
Notes = _notes
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return applications.ToArray();
|
||||
}
|
||||
|
||||
public async Task<string[]> GetApplicationResponseEmailFromJobListing(int JobListingID) {
|
||||
List<string> emailadds = new List<string>();
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT ResponseEmail
|
||||
FROM JobApplication
|
||||
WHERE JobListingID = @JobListingID
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@JobListingID", JobListingID);
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
string _responseemail = reader.GetString("ResponseEmail");
|
||||
emailadds.Add(_responseemail);
|
||||
}
|
||||
}
|
||||
}
|
||||
return emailadds.ToArray();
|
||||
}
|
||||
|
||||
public async Task<Application?> GetApplication(int ApplicationID) {
|
||||
Application? application = null;
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM JobApplication
|
||||
WHERE ID = @ApplicationID
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@ApplicationID", ApplicationID);
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _accountid = reader.GetInt32("AccountID");
|
||||
int _resumeid = reader.GetInt32("ResumeID");
|
||||
int _joblistingid = reader.GetInt32("JobListingID");
|
||||
string _responseemail = reader.GetString("ResponseEmail");
|
||||
DateTime _dateapplied = reader.GetDateTime("DateApplied");
|
||||
string _responsestatus = reader.GetString("ResponseStatus");
|
||||
bool _hasbeenviewed = reader.GetBoolean("HasBeenViewed");
|
||||
int _rating = reader.GetInt32("Rating");
|
||||
string _notes = reader.GetString("Notes");
|
||||
|
||||
application = new Application() {
|
||||
ID = _id,
|
||||
AccountID = _accountid,
|
||||
ResumeID = _resumeid,
|
||||
JobListingID = _joblistingid,
|
||||
ResponseEmail = _responseemail,
|
||||
DateApplied = _dateapplied,
|
||||
ResponseStatus = _responsestatus,
|
||||
HasBeenViewed = _hasbeenviewed,
|
||||
Rating = _rating,
|
||||
Notes = _notes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return application;
|
||||
}
|
||||
|
||||
public async Task SetApplication(Application application) {
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
INSERT INTO JobApplication
|
||||
(ID,AccountID,ResumeID,JobListingID,ResponseEmail,DateApplied,ResponseStatus,HasBeenViewed,Rating,Notes)
|
||||
VALUES
|
||||
(@ID,@AccountID,@ResumeID,@JobListingID,@ResponseEmail,@DateApplied,@ResponseStatus,@HasBeenViewed,@Rating,@Notes)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
AccountID = @AccountID,
|
||||
ResumeID = @ResumeID,
|
||||
JobListingID = @JobListingID,
|
||||
ResponseEmail = @ResponseEmail,
|
||||
ResponseStatus = @ResponseStatus,
|
||||
HasBeenViewed = @HasBeenViewed,
|
||||
Rating = @Rating,
|
||||
Notes = @Notes;
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@ID", application.ID);
|
||||
cmd.Parameters.AddWithValue("@AccountID", application.AccountID);
|
||||
cmd.Parameters.AddWithValue("@ResumeID", application.ResumeID);
|
||||
cmd.Parameters.AddWithValue("@JobListingID", application.JobListingID);
|
||||
cmd.Parameters.AddWithValue("@ResponseEmail", application.ResponseEmail);
|
||||
cmd.Parameters.AddWithValue("@DateApplied", DateTime.UtcNow);
|
||||
cmd.Parameters.AddWithValue("@ResponseStatus", application.ResponseStatus);
|
||||
cmd.Parameters.AddWithValue("@HasBeenViewed", application.HasBeenViewed);
|
||||
cmd.Parameters.AddWithValue("@Rating", application.Rating);
|
||||
cmd.Parameters.AddWithValue("@Notes", application.Notes);
|
||||
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteApplication(int ApplicationID) {
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
MySqlCommand cmd;
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
DELETE FROM JobApplication WHERE ID = @ID;
|
||||
";
|
||||
cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@ID", ApplicationID);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task<Company?> GetCompany( int CompanyID ) {
|
||||
Company? company = null;
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM Company
|
||||
@@ -22,13 +22,14 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
|
||||
using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) {
|
||||
while( await reader.ReadAsync() ) {
|
||||
if( reader == null ) { break; }
|
||||
int _id = reader.GetInt32("ID");
|
||||
string _name = reader.GetString("Name");
|
||||
string _email = reader.GetString("Email");
|
||||
bool _emailVerified = reader.GetBoolean("EmailVerified");
|
||||
string _websiteurl = reader.GetString("WebsiteURL");
|
||||
string _logo = Encoding.UTF8.GetString((byte[])reader["Logo"]);
|
||||
int _jobsclosedsuccessful = reader.GetInt32("JobsClosedSuccessful");
|
||||
int _jobsautoclosed = reader.GetInt32("JobsAutoClosed");
|
||||
string _phone = reader.GetString( "Phone" );
|
||||
string _postalcode = reader.GetString( "PostalCode" );
|
||||
string _country = reader.GetString( "Country" );
|
||||
@@ -43,6 +44,8 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
EmailVerified = _emailVerified,
|
||||
WebsiteURL = _websiteurl,
|
||||
Logo = _logo,
|
||||
JobsAutoClosed = _jobsautoclosed,
|
||||
JobsClosedSuccessful = _jobsclosedsuccessful,
|
||||
Phone = _phone,
|
||||
PostalCode = _postalcode,
|
||||
Country = _country,
|
||||
@@ -58,19 +61,20 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
|
||||
public async Task<int> SetCompany( Company company ) {
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
connection.Open();
|
||||
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
INSERT INTO Company
|
||||
(ID,Name,Email,EmailVerified,WebsiteURL,Logo,Phone,PostalCode,Country,StateOrRegion,City,Description)
|
||||
(ID,Name,Email,EmailVerified,WebsiteURL,Logo,JobsClosedSuccessful,JobsAutoClosed,Phone,PostalCode,Country,StateOrRegion,City,Description)
|
||||
VALUES
|
||||
(@ID,@Name,@Email,@EmailVerified,@WebsiteURL,@Logo,@Phone,@PostalCode,@Country,@StateOrRegion,@City,@Description)
|
||||
(@ID,@Name,@Email,@EmailVerified,@WebsiteURL,@Logo,@JobsClosedSuccessful,@JobsAutoClosed,@Phone,@PostalCode,@Country,@StateOrRegion,@City,@Description)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
Name = @Name,
|
||||
Email = @Email,
|
||||
EmailVerified = @EmailVerified,
|
||||
WebsiteURL = @WebsiteURL,
|
||||
Logo = @Logo,
|
||||
JobsClosedSuccessful = @JobsClosedSuccessful,
|
||||
JobsAutoClosed = @JobsAutoClosed,
|
||||
Phone = @Phone,
|
||||
PostalCode = @PostalCode,
|
||||
Country = @Country,
|
||||
@@ -88,6 +92,8 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
cmd.Parameters.AddWithValue("@EmailVerified", company.EmailVerified);
|
||||
cmd.Parameters.AddWithValue("@WebsiteURL", company.WebsiteURL);
|
||||
cmd.Parameters.AddWithValue("@Logo", Encoding.UTF8.GetBytes(company.Logo));
|
||||
cmd.Parameters.AddWithValue("@JobsClosedSuccessful", company.JobsClosedSuccessful);
|
||||
cmd.Parameters.AddWithValue("@JobsAutoClosed", company.JobsAutoClosed);
|
||||
cmd.Parameters.AddWithValue("@Phone", company.Phone);
|
||||
cmd.Parameters.AddWithValue("@PostalCode", company.PostalCode);
|
||||
cmd.Parameters.AddWithValue("@Country", company.Country);
|
||||
@@ -104,7 +110,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task DeleteCompany( int CompanyID ) {
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
MySqlCommand cmd;
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
|
||||
string command = @"
|
||||
DELETE FROM Company WHERE ID = @ID;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task<Employee?> GetEmployee( int EmployeeID ) {
|
||||
Employee? employee = null;
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM Employee
|
||||
@@ -23,7 +23,6 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
|
||||
using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) {
|
||||
while( await reader.ReadAsync() ) {
|
||||
if( reader == null ) { break; }
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _accountid = reader.GetInt32("AccountID");
|
||||
int _companyid = reader.GetInt32("CompanyID");
|
||||
@@ -66,7 +65,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task<Employee[]> GetEmployeesFromCompany(int CompanyID) {
|
||||
List<Employee> employees = new List<Employee>();
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM Employee
|
||||
@@ -79,7 +78,6 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
if (reader == null) { break; }
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _accountid = reader.GetInt32("AccountID");
|
||||
int _companyid = reader.GetInt32("CompanyID");
|
||||
@@ -122,7 +120,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task<Employee[]> GetEmployeesFromAccount(int AccountID) {
|
||||
List<Employee> employees = new List<Employee>();
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM Employee
|
||||
@@ -135,7 +133,6 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
if (reader == null) { break; }
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _accountid = reader.GetInt32("AccountID");
|
||||
int _companyid = reader.GetInt32("CompanyID");
|
||||
@@ -177,7 +174,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
|
||||
public async Task SetEmployee(Employee employee) {
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
|
||||
string command = @"
|
||||
INSERT INTO Employee
|
||||
@@ -201,7 +198,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task DeleteEmployee( int EmployeeID ) {
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
MySqlCommand cmd;
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
|
||||
string command = @"
|
||||
DELETE FROM Employee WHERE ID = @ID;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BoredCareers.Entities;
|
||||
using BoredCareers.Services.TimerService;
|
||||
using MySql.Data.MySqlClient;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
@@ -9,7 +10,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task<JobListing[]> GetJobListingPage(int PageNumber, int CountPerPage) {
|
||||
List<JobListing> joblistings = new List<JobListing>();
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM JobListing
|
||||
@@ -24,7 +25,6 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
if (reader == null) { break; }
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _companyid = reader.GetInt32("CompanyID");
|
||||
string _title = reader.GetString("Title");
|
||||
@@ -37,6 +37,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
string _jobtype = reader.GetString("JobType");
|
||||
bool _remote = reader.GetBoolean("Remote");
|
||||
string _description = reader.GetString("Description");
|
||||
JobListingSkill[] _skills = await GetJobListingSkills(_id);
|
||||
DateTime _createtime = reader.GetDateTime("CreatedTime");
|
||||
DateTime _modifiedtime = reader.GetDateTime("ModifiedTime");
|
||||
bool _isdeleted = reader.GetBoolean("IsDeleted");
|
||||
@@ -54,6 +55,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
JobType = _jobtype,
|
||||
Remote = _remote,
|
||||
Description = _description,
|
||||
Skills = _skills,
|
||||
CreatedTime = _createtime,
|
||||
ModifiedTime = _modifiedtime,
|
||||
IsDeleted = _isdeleted
|
||||
@@ -67,7 +69,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task<JobListing[]> GetJobListingFromCompany(int CompanyID) {
|
||||
List<JobListing> joblistings = new List<JobListing>(); ;
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM JobListing
|
||||
@@ -79,7 +81,6 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
if (reader == null) { break; }
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _companyid = reader.GetInt32("CompanyID");
|
||||
string _title = reader.GetString("Title");
|
||||
@@ -92,6 +93,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
string _jobtype = reader.GetString("JobType");
|
||||
bool _remote = reader.GetBoolean("Remote");
|
||||
string _description = reader.GetString("Description");
|
||||
JobListingSkill[] _skills = await GetJobListingSkills(_id);
|
||||
DateTime _createtime = reader.GetDateTime("CreatedTime");
|
||||
DateTime _modifiedtime = reader.GetDateTime("ModifiedTime");
|
||||
bool _isdeleted = reader.GetBoolean("IsDeleted");
|
||||
@@ -109,6 +111,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
JobType = _jobtype,
|
||||
Remote = _remote,
|
||||
Description = _description,
|
||||
Skills = _skills,
|
||||
CreatedTime = _createtime,
|
||||
ModifiedTime = _modifiedtime,
|
||||
IsDeleted = _isdeleted
|
||||
@@ -122,7 +125,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task<JobListing?> GetJobListing(int JobListingID) {
|
||||
JobListing? joblisting = null;
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM JobListing
|
||||
@@ -134,7 +137,6 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
if (reader == null) { break; }
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _companyid = reader.GetInt32("CompanyID");
|
||||
string _title = reader.GetString("Title");
|
||||
@@ -147,6 +149,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
string _jobtype = reader.GetString("JobType");
|
||||
bool _remote = reader.GetBoolean("Remote");
|
||||
string _description = reader.GetString("Description");
|
||||
JobListingSkill[] _skills = await GetJobListingSkills(_id);
|
||||
DateTime _createtime = reader.GetDateTime("CreatedTime");
|
||||
DateTime _modifiedtime = reader.GetDateTime("ModifiedTime");
|
||||
bool _isdeleted = reader.GetBoolean("IsDeleted");
|
||||
@@ -164,6 +167,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
JobType = _jobtype,
|
||||
Remote = _remote,
|
||||
Description = _description,
|
||||
Skills = _skills,
|
||||
CreatedTime = _createtime,
|
||||
ModifiedTime = _modifiedtime,
|
||||
IsDeleted = _isdeleted
|
||||
@@ -174,9 +178,35 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
return joblisting;
|
||||
}
|
||||
|
||||
public async Task SetJobListing( JobListing jobListing ) {
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
connection.Open();
|
||||
public async Task<JobListingDTO[]> GetJobListingsPastExipre() {
|
||||
List<JobListingDTO> joblistings = new List<JobListingDTO>();
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT ID, CompanyID
|
||||
FROM JobListing
|
||||
WHERE IsDeleted = FALSE
|
||||
AND CreatedTime < NOW() - INTERVAL 1 MONTH;
|
||||
";
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _companyid = reader.GetInt32("CompanyID");
|
||||
joblistings.Add(new JobListingDTO() {
|
||||
JobListingID = _id,
|
||||
CompanyID = _companyid
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return joblistings.ToArray();
|
||||
}
|
||||
|
||||
public async Task SetJobListing(JobListing jobListing) {
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
await connection.OpenAsync();
|
||||
|
||||
string command = @"
|
||||
INSERT INTO JobListing
|
||||
@@ -195,12 +225,11 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
JobType = @JobType,
|
||||
Remote = @Remote,
|
||||
Description = @Description,
|
||||
CreatedTime = @CreatedTime,
|
||||
ModifiedTime = @ModifiedTime,
|
||||
IsDeleted = @IsDeleted;
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand( command , connection);
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@ID", jobListing.ID);
|
||||
cmd.Parameters.AddWithValue("@CompanyID", jobListing.CompanyID);
|
||||
cmd.Parameters.AddWithValue("@Title", jobListing.Title);
|
||||
@@ -213,25 +242,43 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
cmd.Parameters.AddWithValue("@JobType", jobListing.JobType);
|
||||
cmd.Parameters.AddWithValue("@Remote", jobListing.Remote);
|
||||
cmd.Parameters.AddWithValue("@Description", jobListing.Description);
|
||||
cmd.Parameters.AddWithValue("@CreatedTime", jobListing.CreatedTime.ToUniversalTime());
|
||||
cmd.Parameters.AddWithValue("@ModifiedTime", jobListing.ModifiedTime.ToUniversalTime());
|
||||
cmd.Parameters.AddWithValue("@CreatedTime", DateTime.UtcNow);
|
||||
cmd.Parameters.AddWithValue("@ModifiedTime", DateTime.UtcNow);
|
||||
cmd.Parameters.AddWithValue("@IsDeleted", jobListing.IsDeleted);
|
||||
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
||||
foreach (JobListingSkill cur in jobListing.Skills) {
|
||||
await SetJobListingSkills(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteJobListingsPastExipre() {
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
UPDATE JobListing
|
||||
SET IsDeleted = TRUE
|
||||
WHERE IsDeleted = FALSE
|
||||
AND CreatedTime < NOW() - INTERVAL 1 MONTH;
|
||||
";
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteJobListing( int JobListingID ) {
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
public async Task DeleteJobListing(int JobListingID) {
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
MySqlCommand cmd;
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
|
||||
string command = @"
|
||||
UPDATE JobListing
|
||||
SET IsDeleted = TRUE
|
||||
WHERE ID = @ID;
|
||||
";
|
||||
cmd = new MySqlCommand( command, connection );
|
||||
cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@ID", JobListingID);
|
||||
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
using BoredCareers.Entities;
|
||||
using MySql.Data.MySqlClient;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace BoredCareers.Services.DatabaseService {
|
||||
public partial class DatabaseService {
|
||||
|
||||
public async Task<JobListingSkill[]> GetJobListingSkills(int JobListingID) {
|
||||
List<JobListingSkill> joblistingskills = new List<JobListingSkill>();
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM JobListingSkill
|
||||
WHERE JobListingID = @JobListingID;
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(command, connection);
|
||||
cmd.Parameters.AddWithValue("@JobListingID", JobListingID);
|
||||
|
||||
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
|
||||
while (await reader.ReadAsync()) {
|
||||
if (reader == null) { break; }
|
||||
int _id = reader.GetInt32("ID");
|
||||
int _joblistingid = reader.GetInt32("JobListingID");
|
||||
string _name = reader.GetString("Name");
|
||||
string _description = reader.GetString("Description");
|
||||
|
||||
joblistingskills.Add(new JobListingSkill() {
|
||||
ID = _id,
|
||||
JobListingID = _joblistingid,
|
||||
Name = _name,
|
||||
Description = _description
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return joblistingskills.ToArray();
|
||||
}
|
||||
|
||||
public async Task SetJobListingSkills( JobListingSkill jobListingSkill ) {
|
||||
using( MySqlConnection connection = GetConnection() ) {
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
INSERT INTO JobListing
|
||||
(ID,JobListingID,Name,Description)
|
||||
VALUES
|
||||
(@ID,@JobListingID,@Name,@Description)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
JobListingID = @JobListingID,
|
||||
Name = @Name,
|
||||
Description = @Description;
|
||||
";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand( command , connection);
|
||||
cmd.Parameters.AddWithValue("@ID", jobListingSkill.ID);
|
||||
cmd.Parameters.AddWithValue("@JobListingID", jobListingSkill.JobListingID);
|
||||
cmd.Parameters.AddWithValue("@Name", jobListingSkill.Name);
|
||||
cmd.Parameters.AddWithValue("@Description", jobListingSkill.Description);
|
||||
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace BoredCareers.Services.DatabaseService {
|
||||
public async Task<Resume[]> GetResumes(int AccountID) {
|
||||
List<Resume> resumes = new List<Resume>();
|
||||
using (MySqlConnection connection = GetConnection()) {
|
||||
connection.Open();
|
||||
await connection.OpenAsync();
|
||||
string command = @"
|
||||
SELECT *
|
||||
FROM Resume
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ namespace BoredCareers.Services {
|
||||
// @VerifyPassword
|
||||
// https://mistox.com/api/account/verifyemail?UserName=@UserName&Guid=@VerifyPassword
|
||||
|
||||
public static string VerifyEmailSubject = "Verify Your Email Address";
|
||||
public static string VerifyEmailEmail = @"
|
||||
public static string JobAutoClosedSubject = "Verify Your Email Address";
|
||||
public static string JobAutoClosedEmail = @"
|
||||
<!DOCTYPE html>
|
||||
<html lang=""en"">
|
||||
<head>
|
||||
@@ -1,51 +0,0 @@
|
||||
namespace BoredCareers.Services {
|
||||
public partial class EmailService {
|
||||
|
||||
// @UserName
|
||||
// @ResetPassWord
|
||||
// https://mistox.com/account/resetpassword?UserName=@UserName&ResetPwd=@ResetPassWord
|
||||
|
||||
public static string ResetPasswordSubject = "Password Reset Request";
|
||||
public static string ResetPasswordEmail = @"
|
||||
<!DOCTYPE html>
|
||||
<html lang=""en"">
|
||||
<head>
|
||||
<meta charset=""UTF-8"">
|
||||
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
|
||||
<title>Password Reset</title>
|
||||
</head>
|
||||
<body style=""font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0;"">
|
||||
<table role=""presentation"" style=""width: 100%; background-color: #f4f4f4; padding: 20px 0;"">
|
||||
<tr>
|
||||
<td>
|
||||
<table role=""presentation"" style=""max-width: 600px; width: 100%; background-color: #ffffff; margin: 0 auto; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);"">
|
||||
<tr>
|
||||
<td style=""padding: 20px; text-align: center; background-color: #4CAF50; color: #ffffff; border-top-left-radius: 8px; border-top-right-radius: 8px;"">
|
||||
<h2>Password Reset Request</h2>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=""padding: 20px; text-align: left; font-size: 16px; color: #333333;"">
|
||||
<p>Hi @UserName,</p>
|
||||
<p>We received a request to reset your password. You can reset your password by clicking the button below:</p>
|
||||
<p style=""text-align: center;"">
|
||||
<a href=""https://mistox.com/account/resetpassword?UserName=@UserName&ResetPwd=@ResetPassWord"" style=""background-color: #4CAF50; color: #ffffff; text-decoration: none; padding: 15px 25px; font-size: 16px; border-radius: 5px; display: inline-block;"">Reset Password</a>
|
||||
</p>
|
||||
<p>If you didn't request a password reset, you can safely ignore this email.</p>
|
||||
<p>Best regards</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style=""padding: 10px; text-align: center; background-color: #f4f4f4; color: #888888; font-size: 12px; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;"">
|
||||
<p>If you have any questions, feel free to <a href=""mailto:webmaster@mistox.com"" style=""color: #4CAF50; text-decoration: none;"">contact support</a>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
";
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user