Merge pull request 'working' (#18) from working into main
Docker Build and Release Upload / build (push) Successful in 1m46s

Reviewed-on: #18
This commit was merged in pull request #18.
This commit is contained in:
2025-08-05 04:16:00 +00:00
25 changed files with 738 additions and 198 deletions
+32 -32
View File
@@ -5,42 +5,42 @@ Server:
When a company is created: When a company is created:
Send email -> verify ownership of the email 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: Client:
jobs/new: jobs/new:
Want to add Required skills to help with filtering Job Listing Skills exists but isn't implimented in the UI
When enter is pressed it tries to submit the form Tab doesnt do anything
Should run the whole carosel on enter before the submit is sent
Need to validate input before allowing next step
Want to add completed job listing preview at end of carosel 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: database:
Add Applied Jobs Table 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
View File
@@ -131,6 +131,8 @@ CREATE TABLE IF NOT EXISTS `Company` (
`EmailVerified` boolean DEFAULT 0, `EmailVerified` boolean DEFAULT 0,
`WebsiteURL` varchar(255) DEFAULT NULL, `WebsiteURL` varchar(255) DEFAULT NULL,
`Logo` mediumblob DEFAULT NULL, `Logo` mediumblob DEFAULT NULL,
`JobsClosedSuccessful` int DEFAULT 0,
`JobsAutoClosed` int DEFAULT 0,
`Phone` varchar(20) DEFAULT NULL, `Phone` varchar(20) DEFAULT NULL,
`PostalCode` varchar(20) NOT NULL, `PostalCode` varchar(20) NOT NULL,
`Country` char(2) NOT NULL, `Country` char(2) NOT NULL,
@@ -161,9 +163,36 @@ CREATE TABLE IF NOT EXISTS `JobListing` (
`JobType` varchar(20) NOT NULL, `JobType` varchar(20) NOT NULL,
`Remote` boolean DEFAULT 0, `Remote` boolean DEFAULT 0,
`Description` text NOT NULL, `Description` text NOT NULL,
`CreatedTime` datetime Default NULL, `CreatedTime` datetime DEFAULT NULL,
`ModifiedTime` datetime DEFAULT NULL, `ModifiedTime` datetime DEFAULT NULL,
`IsDeleted` boolean Default 0, `IsDeleted` boolean DEFAULT 0,
PRIMARY KEY (`ID`), PRIMARY KEY (`ID`),
FOREIGN KEY (`CompanyID`) REFERENCES `Company`(`ID`) ON DELETE CASCADE FOREIGN KEY (`CompanyID`) REFERENCES `Company`(`ID`) ON DELETE CASCADE
) AUTO_INCREMENT=1; ) 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;
+12
View File
@@ -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 = "";
}
+2
View File
@@ -5,6 +5,8 @@ export class Company {
public emailVerified: boolean = false; public emailVerified: boolean = false;
public websiteURL: string = ""; public websiteURL: string = "";
public logo: string = ""; public logo: string = "";
public jobsClosedSuccessful: number = 0;
public jobsAutoClosed: number = 0;
public phone: string = ""; public phone: string = "";
public postalCode: string = ""; public postalCode: string = "";
public country: string = ""; // 2 Letter Country Code public country: string = ""; // 2 Letter Country Code
+8
View File
@@ -11,7 +11,15 @@ export class JobListing {
public jobType: string = ""; public jobType: string = "";
public remote: boolean = false; public remote: boolean = false;
public description: string = ""; public description: string = "";
public skills: JobListingSkills[] = [];
public createdTime: Date = new Date(); public createdTime: Date = new Date();
public modifiedTime: Date = new Date(); public modifiedTime: Date = new Date();
public isDeleted: boolean = false; public isDeleted: boolean = false;
} }
export class JobListingSkills {
public id: number | null = null;
public jobListingID: number = 0;
public name: string = "";
public Description: string = "";
}
+50 -50
View File
@@ -21,86 +21,86 @@ export class Resume {
export class ResumeExperience { export class ResumeExperience {
public id: number | null = null; public id: number | null = null;
resumeID: number = 0; public resumeID: number = 0;
jobTitle: string = ""; public jobTitle: string = "";
company: string = ""; public company: string = "";
postalCode: string = ""; public postalCode: string = "";
country: string = ""; public country: string = "";
stateOrRegion: string = ""; public stateOrRegion: string = "";
city: string = ""; public city: string = "";
dateStarted: Date = new Date(); public dateStarted: Date = new Date();
stillEmployed: boolean = false; public stillEmployed: boolean = false;
dateEnded: Date = new Date(); public dateEnded: Date = new Date();
experienceBullets: ResumeExperienceBullet[] = []; public experienceBullets: ResumeExperienceBullet[] = [];
} }
export class ResumeExperienceBullet { export class ResumeExperienceBullet {
public id: number | null = null; public id: number | null = null;
resumeID: number = 0; public resumeID: number = 0;
resumeExperienceID: number = 0; public resumeExperienceID: number = 0;
jobFunction: string = ""; public jobFunction: string = "";
} }
export class ResumeMilitary { export class ResumeMilitary {
public id: number | null = null; public id: number | null = null;
resumeID: number = 0; public resumeID: number = 0;
country: string = ""; public country: string = "";
rank: string = ""; public rank: string = "";
dateStarted: Date = new Date(); public dateStarted: Date = new Date();
stillServing: boolean = false; public stillServing: boolean = false;
dateEnded: Date = new Date(); public dateEnded: Date = new Date();
millitaryBullets: ResumeMilitaryBullet[] = []; public millitaryBullets: ResumeMilitaryBullet[] = [];
} }
export class ResumeMilitaryBullet { export class ResumeMilitaryBullet {
public id: number | null = null; public id: number | null = null;
resumeID: number = 0; public resumeID: number = 0;
resumeMilitaryID: number = 0; public resumeMilitaryID: number = 0;
achievement: string = ""; public achievement: string = "";
description: string = ""; public description: string = "";
} }
export class ResumeEducation { export class ResumeEducation {
public id: number | null = null; public id: number | null = null;
resumeID: number = 0; public resumeID: number = 0;
degreeType: string = ""; public degreeType: string = "";
degreeField: string = ""; public degreeField: string = "";
school: string = ""; public school: string = "";
postalCode: string = ""; public postalCode: string = "";
country: string = ""; public country: string = "";
stateOrRegion: string = ""; public stateOrRegion: string = "";
city: string = ""; public city: string = "";
dateStarted: Date = new Date(); public dateStarted: Date = new Date();
stillStudying: boolean = false; public stillStudying: boolean = false;
dateEnded: Date = new Date(); public dateEnded: Date = new Date();
} }
export class ResumeSkill { export class ResumeSkill {
public id: number | null = null; public id: number | null = null;
resumeID: number = 0; public resumeID: number = 0;
name: string = ""; public name: string = "";
description: string = ""; public description: string = "";
} }
export class ResumeLanguage { export class ResumeLanguage {
public id: number | null = null; public id: number | null = null;
resumeID: number = 0; public resumeID: number = 0;
language: string = ""; public language: string = "";
proficiency: string = ""; public proficiency: string = "";
} }
export class ResumeCertification { export class ResumeCertification {
public id: number | null = null; public id: number | null = null;
resumeID: number = 0; public resumeID: number = 0;
name: string = ""; public name: string = "";
verificationURL: string = ""; public verificationURL: string = "";
description: string = ""; public description: string = "";
} }
export class ResumeProject { export class ResumeProject {
public id: number | null = null; public id: number | null = null;
resumeID: number = 0; public resumeID: number = 0;
name: string = ""; public name: string = "";
url: string = ""; public url: string = "";
description: string = ""; public description: string = "";
} }
@@ -30,4 +30,15 @@ button {
margin: 10px; margin: 10px;
overflow: scroll; overflow: scroll;
padding: 10px; 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>
<div class="content-frame"> <div class="content-frame">
<div *ngIf="Comp != null"> <div *ngIf="Comp != null">
<h1>{{ Comp.name }}</h1> <div class="center-item">
<h1>{{ Comp.email }}</h1> <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.emailVerified }}</h1>
<h1>{{ Comp.websiteURL }}</h1>
<h1>{{ Comp.logo }}</h1>
<h1>{{ Comp.phone }}</h1> <h1>{{ Comp.phone }}</h1>
<h1>{{ Comp.postalCode }}</h1> <h1>{{ Comp.postalCode }}</h1>
<h1>{{ Comp.country }}</h1> <h1>{{ Comp.country }}</h1>
@@ -3,6 +3,19 @@
width: calc(100% - 400px); 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 { .content-frame {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -16,6 +29,7 @@
.floating-frame { .floating-frame {
width: 450px; width: 450px;
height: 200px;
background-color: var(--Mistox-Frame); background-color: var(--Mistox-Frame);
padding: 20px; padding: 20px;
margin: 20px 0; margin: 20px 0;
@@ -41,8 +55,11 @@ hr {
} }
.solution-frame { .solution-frame {
height: 200px;
background-color: blueviolet;
width: calc(100% - 515px); width: calc(100% - 515px);
margin: 20px 0; margin: 20px 0;
text-align: center;
}
.border {
border-bottom: solid 1px #000;
} }
@@ -1,5 +1,11 @@
<div class="center-frame"> <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="floating-frame">
<div class="title-block"> <div class="title-block">
<h1>death by a thousand applicants</h1> <h1>death by a thousand applicants</h1>
@@ -11,11 +17,14 @@
</div> </div>
</div> </div>
<div class="solution-frame"> <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> </div>
<div class="content-frame"> <div class="content-frame border">
<div class="floating-frame"> <div class="floating-frame">
<div class="title-block"> <div class="title-block">
<h1>keyword frenzie</h1> <h1>keyword frenzie</h1>
@@ -27,11 +36,12 @@
</div> </div>
</div> </div>
<div class="solution-frame"> <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> </div>
<div class="content-frame"> <div class="content-frame border">
<div class="floating-frame"> <div class="floating-frame">
<div class="title-block"> <div class="title-block">
<h1>response black-hole</h1> <h1>response black-hole</h1>
@@ -43,11 +53,12 @@
</div> </div>
</div> </div>
<div class="solution-frame"> <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> </div>
<div class="content-frame"> <div class="content-frame border">
<div class="floating-frame"> <div class="floating-frame">
<div class="title-block"> <div class="title-block">
<h1>zombie postings</h1> <h1>zombie postings</h1>
@@ -59,7 +70,8 @@
</div> </div>
</div> </div>
<div class="solution-frame"> <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>
</div> </div>
@@ -75,7 +87,8 @@
</div> </div>
</div> </div>
<div class="solution-frame"> <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> </div>
</div> </div>
+1 -1
View File
@@ -5,7 +5,7 @@
--Mistox-Medium: #890620; --Mistox-Medium: #890620;
--Mistox-Light: #B6465F; --Mistox-Light: #B6465F;
--Mistox-Bright: #FC440F; --Mistox-Bright: #FC440F;
--Mistox-Frame: #FF5A00CC; --Mistox-Frame: #FFE0A5;
--Mistox-Button: #ff9999; --Mistox-Button: #ff9999;
--Mistox-Button-Hover: #ff999977; --Mistox-Button-Hover: #ff999977;
--Mistox-White: #FFF; --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");
}
}
}
+14
View File
@@ -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; } = "";
}
}
+2
View File
@@ -7,6 +7,8 @@ namespace BoredCareers.Entities {
public bool EmailVerified { get; set; } = false; public bool EmailVerified { get; set; } = false;
public string WebsiteURL { get; set; } = ""; public string WebsiteURL { get; set; } = "";
public string Logo { get; set; } = ""; public string Logo { get; set; } = "";
public int JobsClosedSuccessful { get; set; }
public int JobsAutoClosed { get; set; }
public string Phone { get; set; } = ""; public string Phone { get; set; } = "";
public string PostalCode { get; set; } = ""; public string PostalCode { get; set; } = "";
public string Country { get; set; } = ""; // 2 Letter Country Code public string Country { get; set; } = ""; // 2 Letter Country Code
+8
View File
@@ -13,9 +13,17 @@ namespace BoredCareers.Entities {
public string JobType { get; set; } = ""; public string JobType { get; set; } = "";
public bool Remote { get; set; } = false; public bool Remote { get; set; } = false;
public string Description { get; set; } = ""; public string Description { get; set; } = "";
public JobListingSkill[] Skills { get; set; } = [];
public DateTime CreatedTime { get; set; } public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; } public DateTime ModifiedTime { get; set; }
public bool IsDeleted { get; set; } = false; 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
View File
@@ -8,11 +8,11 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography; using System.Security.Cryptography;
using BoredCareers.Services.TimerService;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Disable null warnings becuse string.IsNullOrEmpty checks for NULL or Empty // Disable null warnings becuse string.IsNullOrEmpty checks for NULL or Empty
#pragma warning disable CS8600
#pragma warning disable CS8604 #pragma warning disable CS8604
//////////////////////////////// ////////////////////////////////
@@ -36,8 +36,9 @@ string? _dbpass = Environment.GetEnvironmentVariable("MySQLPass");
string dbPass = !string.IsNullOrEmpty(_dbpass) ? _dbpass : "oasv34$8gpv023dd"; string dbPass = !string.IsNullOrEmpty(_dbpass) ? _dbpass : "oasv34$8gpv023dd";
// Create the database serivice // Create the database serivice
DatabaseService databaseService = new DatabaseService(connectionString: "server=" + dbserver + ";user=" + dbUser + ";database=" + dbdatabase + ";password=" + dbPass + ";port=3306;"); builder.Services.AddSingleton<DatabaseService>(sp =>
builder.Services.Add( new ServiceDescriptor( typeof( DatabaseService ), databaseService ) ); new DatabaseService("server=" + dbserver + ";user=" + dbUser + ";database=" + dbdatabase + ";password=" + dbPass + ";port=3306;")
);
//////////////////////////////// ////////////////////////////////
///////// Email Service //////// ///////// 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 => { builder.Services.AddRateLimiter(options => {
options.AddPolicy("PerUserPolicy", httpContext => { options.AddPolicy("PerUserPolicy", httpContext => {
var userId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value var userId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
?? httpContext.User.Identity?.Name ?? $"ip:{httpContext.Connection.RemoteIpAddress}";
?? httpContext.Connection.RemoteIpAddress?.ToString();
return RateLimitPartition.GetTokenBucketLimiter(userId, key => new TokenBucketRateLimiterOptions { return RateLimitPartition.GetTokenBucketLimiter(userId, key => new TokenBucketRateLimiterOptions {
TokenLimit = 10, // max 10 requests 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.AddControllers();
builder.Services.AddRazorPages();
var app = builder.Build(); var app = builder.Build();
@@ -170,12 +192,14 @@ if( !app.Environment.IsDevelopment() ) {
app.UseDefaultFiles(); app.UseDefaultFiles();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseRateLimiter();
app.UseCors(); app.UseCors();
app.UseRouting(); app.UseRouting();
app.UseAuthentication(); app.UseAuthentication();
app.MapControllers().RequireRateLimiting("perUserPolicy"); app.MapControllers();
app.MapFallbackToFile("index.html"); 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();
}
}
}
}
+13 -7
View File
@@ -10,7 +10,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task<Company?> GetCompany( int CompanyID ) { public async Task<Company?> GetCompany( int CompanyID ) {
Company? company = null; Company? company = null;
using( MySqlConnection connection = GetConnection() ) { using( MySqlConnection connection = GetConnection() ) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
SELECT * SELECT *
FROM Company FROM Company
@@ -22,13 +22,14 @@ namespace BoredCareers.Services.DatabaseService {
using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) {
while( await reader.ReadAsync() ) { while( await reader.ReadAsync() ) {
if( reader == null ) { break; }
int _id = reader.GetInt32("ID"); int _id = reader.GetInt32("ID");
string _name = reader.GetString("Name"); string _name = reader.GetString("Name");
string _email = reader.GetString("Email"); string _email = reader.GetString("Email");
bool _emailVerified = reader.GetBoolean("EmailVerified"); bool _emailVerified = reader.GetBoolean("EmailVerified");
string _websiteurl = reader.GetString("WebsiteURL"); string _websiteurl = reader.GetString("WebsiteURL");
string _logo = Encoding.UTF8.GetString((byte[])reader["Logo"]); string _logo = Encoding.UTF8.GetString((byte[])reader["Logo"]);
int _jobsclosedsuccessful = reader.GetInt32("JobsClosedSuccessful");
int _jobsautoclosed = reader.GetInt32("JobsAutoClosed");
string _phone = reader.GetString( "Phone" ); string _phone = reader.GetString( "Phone" );
string _postalcode = reader.GetString( "PostalCode" ); string _postalcode = reader.GetString( "PostalCode" );
string _country = reader.GetString( "Country" ); string _country = reader.GetString( "Country" );
@@ -43,6 +44,8 @@ namespace BoredCareers.Services.DatabaseService {
EmailVerified = _emailVerified, EmailVerified = _emailVerified,
WebsiteURL = _websiteurl, WebsiteURL = _websiteurl,
Logo = _logo, Logo = _logo,
JobsAutoClosed = _jobsautoclosed,
JobsClosedSuccessful = _jobsclosedsuccessful,
Phone = _phone, Phone = _phone,
PostalCode = _postalcode, PostalCode = _postalcode,
Country = _country, Country = _country,
@@ -58,19 +61,20 @@ namespace BoredCareers.Services.DatabaseService {
public async Task<int> SetCompany( Company company ) { public async Task<int> SetCompany( Company company ) {
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
INSERT INTO Company 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 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 ON DUPLICATE KEY UPDATE
Name = @Name, Name = @Name,
Email = @Email, Email = @Email,
EmailVerified = @EmailVerified, EmailVerified = @EmailVerified,
WebsiteURL = @WebsiteURL, WebsiteURL = @WebsiteURL,
Logo = @Logo, Logo = @Logo,
JobsClosedSuccessful = @JobsClosedSuccessful,
JobsAutoClosed = @JobsAutoClosed,
Phone = @Phone, Phone = @Phone,
PostalCode = @PostalCode, PostalCode = @PostalCode,
Country = @Country, Country = @Country,
@@ -88,6 +92,8 @@ namespace BoredCareers.Services.DatabaseService {
cmd.Parameters.AddWithValue("@EmailVerified", company.EmailVerified); cmd.Parameters.AddWithValue("@EmailVerified", company.EmailVerified);
cmd.Parameters.AddWithValue("@WebsiteURL", company.WebsiteURL); cmd.Parameters.AddWithValue("@WebsiteURL", company.WebsiteURL);
cmd.Parameters.AddWithValue("@Logo", Encoding.UTF8.GetBytes(company.Logo)); 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("@Phone", company.Phone);
cmd.Parameters.AddWithValue("@PostalCode", company.PostalCode); cmd.Parameters.AddWithValue("@PostalCode", company.PostalCode);
cmd.Parameters.AddWithValue("@Country", company.Country); cmd.Parameters.AddWithValue("@Country", company.Country);
@@ -104,7 +110,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task DeleteCompany( int CompanyID ) { public async Task DeleteCompany( int CompanyID ) {
using( MySqlConnection connection = GetConnection() ) { using( MySqlConnection connection = GetConnection() ) {
MySqlCommand cmd; MySqlCommand cmd;
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
DELETE FROM Company WHERE ID = @ID; DELETE FROM Company WHERE ID = @ID;
@@ -10,7 +10,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task<Employee?> GetEmployee( int EmployeeID ) { public async Task<Employee?> GetEmployee( int EmployeeID ) {
Employee? employee = null; Employee? employee = null;
using( MySqlConnection connection = GetConnection() ) { using( MySqlConnection connection = GetConnection() ) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
SELECT * SELECT *
FROM Employee FROM Employee
@@ -23,7 +23,6 @@ namespace BoredCareers.Services.DatabaseService {
using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) { using( DbDataReader reader = await cmd.ExecuteReaderAsync() ) {
while( await reader.ReadAsync() ) { while( await reader.ReadAsync() ) {
if( reader == null ) { break; }
int _id = reader.GetInt32("ID"); int _id = reader.GetInt32("ID");
int _accountid = reader.GetInt32("AccountID"); int _accountid = reader.GetInt32("AccountID");
int _companyid = reader.GetInt32("CompanyID"); int _companyid = reader.GetInt32("CompanyID");
@@ -66,7 +65,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task<Employee[]> GetEmployeesFromCompany(int CompanyID) { public async Task<Employee[]> GetEmployeesFromCompany(int CompanyID) {
List<Employee> employees = new List<Employee>(); List<Employee> employees = new List<Employee>();
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
SELECT * SELECT *
FROM Employee FROM Employee
@@ -79,7 +78,6 @@ namespace BoredCareers.Services.DatabaseService {
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) { using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
if (reader == null) { break; }
int _id = reader.GetInt32("ID"); int _id = reader.GetInt32("ID");
int _accountid = reader.GetInt32("AccountID"); int _accountid = reader.GetInt32("AccountID");
int _companyid = reader.GetInt32("CompanyID"); int _companyid = reader.GetInt32("CompanyID");
@@ -122,7 +120,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task<Employee[]> GetEmployeesFromAccount(int AccountID) { public async Task<Employee[]> GetEmployeesFromAccount(int AccountID) {
List<Employee> employees = new List<Employee>(); List<Employee> employees = new List<Employee>();
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
SELECT * SELECT *
FROM Employee FROM Employee
@@ -135,7 +133,6 @@ namespace BoredCareers.Services.DatabaseService {
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) { using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
if (reader == null) { break; }
int _id = reader.GetInt32("ID"); int _id = reader.GetInt32("ID");
int _accountid = reader.GetInt32("AccountID"); int _accountid = reader.GetInt32("AccountID");
int _companyid = reader.GetInt32("CompanyID"); int _companyid = reader.GetInt32("CompanyID");
@@ -177,7 +174,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task SetEmployee(Employee employee) { public async Task SetEmployee(Employee employee) {
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
INSERT INTO Employee INSERT INTO Employee
@@ -201,7 +198,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task DeleteEmployee( int EmployeeID ) { public async Task DeleteEmployee( int EmployeeID ) {
using( MySqlConnection connection = GetConnection() ) { using( MySqlConnection connection = GetConnection() ) {
MySqlCommand cmd; MySqlCommand cmd;
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
DELETE FROM Employee WHERE ID = @ID; DELETE FROM Employee WHERE ID = @ID;
@@ -1,4 +1,5 @@
using BoredCareers.Entities; using BoredCareers.Entities;
using BoredCareers.Services.TimerService;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
using System.Data; using System.Data;
using System.Data.Common; using System.Data.Common;
@@ -9,7 +10,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task<JobListing[]> GetJobListingPage(int PageNumber, int CountPerPage) { public async Task<JobListing[]> GetJobListingPage(int PageNumber, int CountPerPage) {
List<JobListing> joblistings = new List<JobListing>(); List<JobListing> joblistings = new List<JobListing>();
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
SELECT * SELECT *
FROM JobListing FROM JobListing
@@ -24,7 +25,6 @@ namespace BoredCareers.Services.DatabaseService {
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) { using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
if (reader == null) { break; }
int _id = reader.GetInt32("ID"); int _id = reader.GetInt32("ID");
int _companyid = reader.GetInt32("CompanyID"); int _companyid = reader.GetInt32("CompanyID");
string _title = reader.GetString("Title"); string _title = reader.GetString("Title");
@@ -37,6 +37,7 @@ namespace BoredCareers.Services.DatabaseService {
string _jobtype = reader.GetString("JobType"); string _jobtype = reader.GetString("JobType");
bool _remote = reader.GetBoolean("Remote"); bool _remote = reader.GetBoolean("Remote");
string _description = reader.GetString("Description"); string _description = reader.GetString("Description");
JobListingSkill[] _skills = await GetJobListingSkills(_id);
DateTime _createtime = reader.GetDateTime("CreatedTime"); DateTime _createtime = reader.GetDateTime("CreatedTime");
DateTime _modifiedtime = reader.GetDateTime("ModifiedTime"); DateTime _modifiedtime = reader.GetDateTime("ModifiedTime");
bool _isdeleted = reader.GetBoolean("IsDeleted"); bool _isdeleted = reader.GetBoolean("IsDeleted");
@@ -54,6 +55,7 @@ namespace BoredCareers.Services.DatabaseService {
JobType = _jobtype, JobType = _jobtype,
Remote = _remote, Remote = _remote,
Description = _description, Description = _description,
Skills = _skills,
CreatedTime = _createtime, CreatedTime = _createtime,
ModifiedTime = _modifiedtime, ModifiedTime = _modifiedtime,
IsDeleted = _isdeleted IsDeleted = _isdeleted
@@ -67,7 +69,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task<JobListing[]> GetJobListingFromCompany(int CompanyID) { public async Task<JobListing[]> GetJobListingFromCompany(int CompanyID) {
List<JobListing> joblistings = new List<JobListing>(); ; List<JobListing> joblistings = new List<JobListing>(); ;
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
SELECT * SELECT *
FROM JobListing FROM JobListing
@@ -79,7 +81,6 @@ namespace BoredCareers.Services.DatabaseService {
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) { using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
if (reader == null) { break; }
int _id = reader.GetInt32("ID"); int _id = reader.GetInt32("ID");
int _companyid = reader.GetInt32("CompanyID"); int _companyid = reader.GetInt32("CompanyID");
string _title = reader.GetString("Title"); string _title = reader.GetString("Title");
@@ -92,6 +93,7 @@ namespace BoredCareers.Services.DatabaseService {
string _jobtype = reader.GetString("JobType"); string _jobtype = reader.GetString("JobType");
bool _remote = reader.GetBoolean("Remote"); bool _remote = reader.GetBoolean("Remote");
string _description = reader.GetString("Description"); string _description = reader.GetString("Description");
JobListingSkill[] _skills = await GetJobListingSkills(_id);
DateTime _createtime = reader.GetDateTime("CreatedTime"); DateTime _createtime = reader.GetDateTime("CreatedTime");
DateTime _modifiedtime = reader.GetDateTime("ModifiedTime"); DateTime _modifiedtime = reader.GetDateTime("ModifiedTime");
bool _isdeleted = reader.GetBoolean("IsDeleted"); bool _isdeleted = reader.GetBoolean("IsDeleted");
@@ -109,6 +111,7 @@ namespace BoredCareers.Services.DatabaseService {
JobType = _jobtype, JobType = _jobtype,
Remote = _remote, Remote = _remote,
Description = _description, Description = _description,
Skills = _skills,
CreatedTime = _createtime, CreatedTime = _createtime,
ModifiedTime = _modifiedtime, ModifiedTime = _modifiedtime,
IsDeleted = _isdeleted IsDeleted = _isdeleted
@@ -122,7 +125,7 @@ namespace BoredCareers.Services.DatabaseService {
public async Task<JobListing?> GetJobListing(int JobListingID) { public async Task<JobListing?> GetJobListing(int JobListingID) {
JobListing? joblisting = null; JobListing? joblisting = null;
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
SELECT * SELECT *
FROM JobListing FROM JobListing
@@ -134,7 +137,6 @@ namespace BoredCareers.Services.DatabaseService {
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) { using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) { while (await reader.ReadAsync()) {
if (reader == null) { break; }
int _id = reader.GetInt32("ID"); int _id = reader.GetInt32("ID");
int _companyid = reader.GetInt32("CompanyID"); int _companyid = reader.GetInt32("CompanyID");
string _title = reader.GetString("Title"); string _title = reader.GetString("Title");
@@ -147,6 +149,7 @@ namespace BoredCareers.Services.DatabaseService {
string _jobtype = reader.GetString("JobType"); string _jobtype = reader.GetString("JobType");
bool _remote = reader.GetBoolean("Remote"); bool _remote = reader.GetBoolean("Remote");
string _description = reader.GetString("Description"); string _description = reader.GetString("Description");
JobListingSkill[] _skills = await GetJobListingSkills(_id);
DateTime _createtime = reader.GetDateTime("CreatedTime"); DateTime _createtime = reader.GetDateTime("CreatedTime");
DateTime _modifiedtime = reader.GetDateTime("ModifiedTime"); DateTime _modifiedtime = reader.GetDateTime("ModifiedTime");
bool _isdeleted = reader.GetBoolean("IsDeleted"); bool _isdeleted = reader.GetBoolean("IsDeleted");
@@ -164,6 +167,7 @@ namespace BoredCareers.Services.DatabaseService {
JobType = _jobtype, JobType = _jobtype,
Remote = _remote, Remote = _remote,
Description = _description, Description = _description,
Skills = _skills,
CreatedTime = _createtime, CreatedTime = _createtime,
ModifiedTime = _modifiedtime, ModifiedTime = _modifiedtime,
IsDeleted = _isdeleted IsDeleted = _isdeleted
@@ -174,9 +178,35 @@ namespace BoredCareers.Services.DatabaseService {
return joblisting; return joblisting;
} }
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) { public async Task SetJobListing(JobListing jobListing) {
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
INSERT INTO JobListing INSERT INTO JobListing
@@ -195,7 +225,6 @@ namespace BoredCareers.Services.DatabaseService {
JobType = @JobType, JobType = @JobType,
Remote = @Remote, Remote = @Remote,
Description = @Description, Description = @Description,
CreatedTime = @CreatedTime,
ModifiedTime = @ModifiedTime, ModifiedTime = @ModifiedTime,
IsDeleted = @IsDeleted; IsDeleted = @IsDeleted;
"; ";
@@ -213,18 +242,36 @@ namespace BoredCareers.Services.DatabaseService {
cmd.Parameters.AddWithValue("@JobType", jobListing.JobType); cmd.Parameters.AddWithValue("@JobType", jobListing.JobType);
cmd.Parameters.AddWithValue("@Remote", jobListing.Remote); cmd.Parameters.AddWithValue("@Remote", jobListing.Remote);
cmd.Parameters.AddWithValue("@Description", jobListing.Description); cmd.Parameters.AddWithValue("@Description", jobListing.Description);
cmd.Parameters.AddWithValue("@CreatedTime", jobListing.CreatedTime.ToUniversalTime()); cmd.Parameters.AddWithValue("@CreatedTime", DateTime.UtcNow);
cmd.Parameters.AddWithValue("@ModifiedTime", jobListing.ModifiedTime.ToUniversalTime()); cmd.Parameters.AddWithValue("@ModifiedTime", DateTime.UtcNow);
cmd.Parameters.AddWithValue("@IsDeleted", jobListing.IsDeleted); cmd.Parameters.AddWithValue("@IsDeleted", jobListing.IsDeleted);
await cmd.ExecuteNonQueryAsync(); 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) { public async Task DeleteJobListing(int JobListingID) {
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
MySqlCommand cmd; MySqlCommand cmd;
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
UPDATE JobListing UPDATE JobListing
@@ -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) { public async Task<Resume[]> GetResumes(int AccountID) {
List<Resume> resumes = new List<Resume>(); List<Resume> resumes = new List<Resume>();
using (MySqlConnection connection = GetConnection()) { using (MySqlConnection connection = GetConnection()) {
connection.Open(); await connection.OpenAsync();
string command = @" string command = @"
SELECT * SELECT *
FROM Resume FROM Resume
@@ -5,8 +5,8 @@ namespace BoredCareers.Services {
// @VerifyPassword // @VerifyPassword
// https://mistox.com/api/account/verifyemail?UserName=@UserName&Guid=@VerifyPassword // https://mistox.com/api/account/verifyemail?UserName=@UserName&Guid=@VerifyPassword
public static string VerifyEmailSubject = "Verify Your Email Address"; public static string JobAutoClosedSubject = "Verify Your Email Address";
public static string VerifyEmailEmail = @" public static string JobAutoClosedEmail = @"
<!DOCTYPE html> <!DOCTYPE html>
<html lang=""en""> <html lang=""en"">
<head> <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>
";
}
}