working #27

Merged
derek merged 11 commits from working into main 2025-08-11 21:28:31 -07:00
21 changed files with 540 additions and 45 deletions
+6 -3
View File
@@ -21,6 +21,9 @@ Server:
Need to update notification email
Create page to notify cx that their work email has been verified
Server.csproj:
Find a way to keep all the libraries up to date
Client:
jobs/editor:
Job Listing Skills exists but isn't implimented in the UI
@@ -45,14 +48,14 @@ Client:
Setup QueryParam's for Edit and New
Edit employees not implimented yet
resume/editor:
Not fully tested yet
Company:
Need to impliment Add employee
Need to impliment Remove employee
Edit Company -> Dont allow edit of company email due to it being verified
All:
Make sure im using the new NG format for for and if
database:
Add Applied Jobs Table
+2
View File
@@ -6,6 +6,7 @@ USE `boredcareers`;
CREATE TABLE IF NOT EXISTS `Resume` (
`ID` int NOT NULL AUTO_INCREMENT,
`AccountID` int NOT NULL,
`Title` varchar(100) NOT NULL,
`Name` varchar(100) NOT NULL,
`Field` varchar(100) DEFAULT NULL,
`Email` varchar(255) NOT NULL,
@@ -47,6 +48,7 @@ CREATE TABLE IF NOT EXISTS `Resume` (
CREATE TABLE IF NOT EXISTS `ResumeMilitary` (
`ID` int NOT NULL AUTO_INCREMENT,
`ResumeID` int NOT NULL,
`Veteran` boolean DEFAULT 0,
`Country` char(2) NOT NULL,
`Rank` varchar(50) NOT NULL,
`DateStarted` date NOT NULL,
+12 -7
View File
@@ -1,25 +1,30 @@
<div class="top-bar">
<div class="top-bar-buttons">
<a class="nav-button" routerLink="/jobs" routerLinkActive="active">JOB BOARD</a>
@if (auth.isLoggedIn){
<a class="nav-button" routerLink="/resumes" routerLinkActive="active">RESUMES</a>
<a class="nav-button" routerLink="/company" routerLinkActive="active">COMPANIES</a>
}
</div>
<a class="top-bar-logo" routerLink="">
<img class="top-bar-logo" style="margin: 0;" src="img/logo-full.png" />
</a>
<div *ngIf="auth.isLoggedIn" class="top-bar-buttons flex-right">
@if (auth.isLoggedIn){
<div class="top-bar-buttons flex-right">
<a class="nav-button nav-button-login" href="https://auth.mistox.com/"><span>{{ auth.loggedInUser.userName.toUpperCase() }}</span></a>
<a class="nav-button nav-button-login" href="/api/account/logout"><span>LOGOUT</span></a>
</div>
<div *ngIf="!auth.isLoggedIn" class="top-bar-buttons flex-right">
<!-- Testing Login -->
<a *ngIf="devMode" class="nav-button nav-button-login" href="https://auth.mistox.com/account/login?returnURL=http://localhost:5000/"><span>Testing Login</span></a>
<!-- Testing Login -->
} @else {
<div class="top-bar-buttons flex-right">
@if (devMode){
<a class="nav-button nav-button-login" href="https://auth.mistox.com/account/login?returnURL=http://localhost:5000/"><span>Testing Login</span></a>
<a class="nav-button nav-button-login" href="https://auth.mistox.com/account/register?returnURL=http://localhost:5000/"><span>REGISTER</span></a>
} @else {
<a class="nav-button nav-button-login" href="https://auth.mistox.com/account/login?returnURL=https://boredcareers.com/"><span>LOGIN</span></a>
<a class="nav-button nav-button-login" href="https://auth.mistox.com/account/register?returnURL=https://boredcareers.com/"><span>REGISTER</span></a>
}
</div>
}
</div>
<div class="content">
<router-outlet></router-outlet>
+2
View File
@@ -7,6 +7,7 @@ import { JobEditorComponent } from './pages/jobs/editor/jobeditor.component';
import { CompanyEditorComponent } from './pages/company/editor/editor.component';
import { JobViewerComponent } from './pages/jobs/viewer/jobviewer.component';
import { CompanyComponent } from './pages/company/company.component';
import { ResumesEditorComponent } from './pages/resumes/editor/editor.component';
export const routes: Routes = [
@@ -15,6 +16,7 @@ export const routes: Routes = [
// Resumes
{ path: "resumes", component: ResumesComponent },
{ path: "resumes/editor", component: ResumesEditorComponent },
// Jobs
{ path: "jobs", component: JobsComponent },
+35 -12
View File
@@ -1,6 +1,9 @@
export class Resume {
public id: number | null = null;
public accountID: number = 0;
public accountID: number | null = null;
public title: string = "";
public name: string = "";
public field: string = "";
public email: string = "";
@@ -17,11 +20,13 @@ export class Resume {
public languages: ResumeLanguage[] = [];
public certification: ResumeCertification[] = [];
public projects: ResumeProject[] = [];
public trackUUID: string = crypto.randomUUID();
}
export class ResumeExperience {
public id: number | null = null;
public resumeID: number = 0;
public resumeID: number | null = null;
public jobTitle: string = "";
public company: string = "";
public postalCode: string = "";
@@ -32,37 +37,46 @@ export class ResumeExperience {
public stillEmployed: boolean = false;
public dateEnded: Date = new Date();
public experienceBullets: ResumeExperienceBullet[] = [];
public trackUUID: string = crypto.randomUUID();
}
export class ResumeExperienceBullet {
public id: number | null = null;
public resumeID: number = 0;
public resumeExperienceID: number = 0;
public resumeID: number | null = null;
public resumeExperienceID: number | null = null;
public jobFunction: string = "";
public trackUUID: string = crypto.randomUUID();
}
export class ResumeMilitary {
public id: number | null = null;
public resumeID: number = 0;
public resumeID: number | null = null;
public veteran: boolean = false;
public country: string = "";
public rank: string = "";
public dateStarted: Date = new Date();
public stillServing: boolean = false;
public dateEnded: Date = new Date();
public millitaryBullets: ResumeMilitaryBullet[] = [];
public trackUUID: string = crypto.randomUUID();
}
export class ResumeMilitaryBullet {
public id: number | null = null;
public resumeID: number = 0;
public resumeMilitaryID: number = 0;
public resumeID: number | null = null;
public resumeMilitaryID: number | null = null;
public achievement: string = "";
public description: string = "";
public trackUUID: string = crypto.randomUUID();
}
export class ResumeEducation {
public id: number | null = null;
public resumeID: number = 0;
public resumeID: number | null = null;
public degreeType: string = "";
public degreeField: string = "";
public school: string = "";
@@ -73,34 +87,43 @@ export class ResumeEducation {
public dateStarted: Date = new Date();
public stillStudying: boolean = false;
public dateEnded: Date = new Date();
public trackUUID: string = crypto.randomUUID();
}
export class ResumeSkill {
public id: number | null = null;
public resumeID: number = 0;
public resumeID: number | null = null;
public name: string = "";
public description: string = "";
public trackUUID: string = crypto.randomUUID();
}
export class ResumeLanguage {
public id: number | null = null;
public resumeID: number = 0;
public resumeID: number | null = null;
public language: string = "";
public proficiency: string = "";
public trackUUID: string = crypto.randomUUID();
}
export class ResumeCertification {
public id: number | null = null;
public resumeID: number = 0;
public resumeID: number | null = null;
public name: string = "";
public verificationURL: string = "";
public description: string = "";
public trackUUID: string = crypto.randomUUID();
}
export class ResumeProject {
public id: number | null = null;
public resumeID: number = 0;
public resumeID: number | null = null;
public name: string = "";
public url: string = "";
public description: string = "";
public trackUUID: string = crypto.randomUUID();
}
@@ -28,6 +28,11 @@ export class CompanyComponent {
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
this.title.setTitle("Companies | BoredCareers");
if (!auth.isLoggedIn){
router.navigate(["/"]);
}
http.get<Employee[]>("api/employee/").subscribe({
next: data => {
this.Employers = data;
@@ -25,6 +25,10 @@ export class CompanyEditorComponent {
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
this.title.setTitle("Company - Editor | BoredCareers");
if (!auth.isLoggedIn){
router.navigate(["/"]);
}
// Query param CompanyID -> Edit
// Query param null -> New
};
@@ -27,6 +27,10 @@ export class JobEditorComponent {
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
this.title.setTitle("Jobs - Editor | BoredCareers");
if (!auth.isLoggedIn){
router.navigate(["/"]);
}
this.route.queryParams.subscribe(params => {
const CompanyID = params['CompanyID'] ? +params['CompanyID'] : null;
const JobID = params['JobID'] ? +params['JobID'] : null;
@@ -0,0 +1,10 @@
.resume-section {
background-color: #DDDDDD;
margin: 10px;
}
.resume-sub-section {
border: 1px solid #666666;
margin: 5px;
padding: 10px;
}
@@ -0,0 +1,145 @@
<form (ngSubmit)="SubmitForm(resume)">
<!-- Resume Header -->
<div class="resume-section">
<input name="resumetitle" [(ngModel)]="resume.title" type="text" placeholder="Resume 1" />
<input name="resumename" [(ngModel)]="resume.name" type="text" placeholder="John Doe" />
<input name="resumefield" [(ngModel)]="resume.field" type="text" placeholder="Data Scientist" />
<input name="resumeemail" [(ngModel)]="resume.email" type="email" placeholder="no-reply@mistox.com" />
<input name="resumephoneNumber" [(ngModel)]="resume.phoneNumber" type="tel" placeholder="+1 800-000-0000" />
<input name="resumepostalCode" [(ngModel)]="resume.postalCode" type="text" placeholder="92020" />
<input name="resumestateOrRegion" [(ngModel)]="resume.stateOrRegion" type="text" placeholder="CA" />
<input name="resumecity" [(ngModel)]="resume.city" type="text" placeholder="San Diego" />
<h1>Public: </h1><input name="active" [(ngModel)]="resume.isActive" type="checkbox" />
</div>
<!-- Experience -->
<div class="resume-section">
<button type="button" (click)="addExperience()">ADD Experience</button>
@for(experience of resume.experience; track experience.trackUUID ){
<div class="resume-sub-section">
<input name="experiencejobTitle" [(ngModel)]="experience.jobTitle" type="text" placeholder="Data Entry Clerk" />
<input name="experiencecompany" [(ngModel)]="experience.company" type="text" placeholder="San Diego Gas Electric" />
<input name="experiencepostalCode" [(ngModel)]="experience.postalCode" type="text" placeholder="92020" />
<input name="experiencecountry" [(ngModel)]="experience.country" type="text" placeholder="US" />
<input name="experiencestateOrRegion" [(ngModel)]="experience.stateOrRegion" type="text" placeholder="CA" />
<input name="experiencecity" [(ngModel)]="experience.city" type="text" placeholder="San Diego" />
<input name="experiencedateStarted" [(ngModel)]="experience.dateStarted" type="date" />
<input name="experiencestillEmployed" [(ngModel)]="experience.stillEmployed" type="checkbox" />
@if(!experience.stillEmployed){
<input name="experiencedateEnded" [(ngModel)]="experience.dateEnded" type="date" />
}
<button type="button" (click)="addJobFunction(experience)">Add jobFunction</button>
@for(bullet of experience.experienceBullets; track bullet.trackUUID){
<div>
<textarea name="bulletjobFunction" [(ngModel)]="bullet.jobFunction" placeholder="Processed database transactions" ></textarea>
<button type="button" (click)="delJobFunction(experience, bullet)">DEL jobFunction</button>
</div>
}
<button type="button" (click)="delExperience(experience)">DEL Experience</button>
</div>
}
</div>
<!-- Military -->
<div class="resume-section">
<h1>Is Veteran: </h1><input name="veteran" [(ngModel)]="resume.military.veteran" type="checkbox" />
@if(resume.military.veteran){
<input name="militarycountry" [(ngModel)]="resume.military.country" type="text" placeholder="US" />
<input name="militaryrank" [(ngModel)]="resume.military.rank" type="text" placeholder="PVT" />
<input name="militarydateStarted" [(ngModel)]="resume.military.dateStarted" type="date" />
<h1>Still Serving: </h1><input name="stillServing" [(ngModel)]="resume.military.stillServing" type="checkbox" />
@if (!resume.military.stillServing){
<input name="dateEnded" [(ngModel)]="resume.military.dateEnded" type="date" />
}
<button type="button" (click)="addMillitaryBullet()">Add Millitary Task</button>
@for(military of resume.military.millitaryBullets; track military.trackUUID ){
<div>
<input name="militaryachievement" [(ngModel)]="military.achievement" type="text" placeholder="Deployed Kuwait" />
<textarea name="militarydescription" [(ngModel)]="military.description" placeholder="Delivered goods line-hall" ></textarea>
<button type="button" (click)="delMillitaryBullet(military)">DEL Military Task</button>
</div>
}
}
</div>
<!-- Education -->
<div class="resume-section">
<button type="button" (click)="addEducation()">ADD Education</button>
@for(education of resume.education; track education.trackUUID){
<div>
<input name="educationdegreeType" [(ngModel)]="education.degreeType" type="text" placeholder="Masters" />
<input name="educationdegreeField" [(ngModel)]="education.degreeField" type="text" placeholder="Computer Science" />
<input name="educationschool" [(ngModel)]="education.school" type="text" placeholder="WGU" />
<input name="educationpostalCode" [(ngModel)]="education.postalCode" type="text" placeholder="84107" />
<input name="educationcountry" [(ngModel)]="education.country" type="text" placeholder="US" />
<input name="educationstateOrRegion" [(ngModel)]="education.stateOrRegion" type="text" placeholder="UT" />
<input name="educationcity" [(ngModel)]="education.city" type="text" placeholder="Salt Lake City" />
<input name="educationdateStarted" [(ngModel)]="education.dateStarted" type="date" />
<input name="educationstillStudying" [(ngModel)]="education.stillStudying" type="checkbox" />
@if (!education.stillStudying){
<input name="educationdateEnded" [(ngModel)]="education.dateEnded" type="date" />
}
<button type="button" (click)="delEducation(education)">DEL Education</button>
</div>
}
</div>
<!-- Skill -->
<div class="resume-section">
<button type="button" (click)="addSkill()">ADD Skill</button>
@for(skill of resume.skills; track skill.trackUUID){
<div>
<input name="skillname" [(ngModel)]="skill.name" type="text" placeholder="Angular JS" />
<textarea name="skilldescription" [(ngModel)]="skill.description" placeholder="Built this entire website using Angular JS"></textarea>
<button type="button" (click)="delSkill(skill)">DEL Skill</button>
</div>
}
</div>
<!-- Language -->
<div class="resume-section">
<button type="button" (click)="addLanguage()">ADD Language</button>
@for(language of resume.languages; track language.trackUUID){
<div>
<input name="languagelanguage" [(ngModel)]="language.language" type="text" placeholder="Spanish" />
<input name="languageproficiency" [(ngModel)]="language.proficiency" type="text" placeholder="casual speaking" />
<button type="button" (click)="delLanguage(language)">DEL Language</button>
</div>
}
</div>
<!-- Certification -->
<div class="resume-section">
<button type="button" (click)="addCert()">ADD Certification</button>
@for(cert of resume.certification; track cert.trackUUID){
<div>
<input name="certname" [(ngModel)]="cert.name" type="text" placeholder="Comptia A+" />
<input name="certverificationURL" [(ngModel)]="cert.verificationURL" type="text" placeholder="https://certmaster.com/certid" />
<textarea name="certdescription" [(ngModel)]="cert.description" placeholder="Into to information technology"></textarea>
<button type="button" (click)="delCert(cert)">DEL Certification</button>
</div>
}
</div>
<!-- Project -->
<div class="resume-section">
<button type="button" (click)="addProject()">ADD Project</button>
@for(proj of resume.projects; track proj.trackUUID){
<div>
<input name="projname" [(ngModel)]="proj.name" type="text" placeholder="boredcareers" />
<input name="projurl" [(ngModel)]="proj.url" type="text" placeholder="mistox.com" />
<textarea name="projdescription" [(ngModel)]="proj.description" placeholder="the project that your currently viewing"></textarea>
<button type="button" (click)="delProject(proj)">DEL Project</button>
</div>
}
</div>
@if (isNewResume){
<button type="submit">CREATE NEW RESUME</button>
} @else {
<button type="submit">UPDATE RESUME</button>
}
</form>
@@ -0,0 +1,162 @@
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { Router, ActivatedRoute, RouterModule } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { Resume, ResumeCertification, ResumeEducation, ResumeExperience, ResumeExperienceBullet, ResumeLanguage, ResumeMilitaryBullet, ResumeProject, ResumeSkill } from 'app/models/Resume';
import { Authentication } from 'app/services/Authentication';
import { HomeComponent } from "app/pages/home/home.component";
@Component({
selector: 'main-resume-editor',
templateUrl: './editor.component.html',
styleUrls: [ './editor.component.css' ],
imports: [FormsModule, CommonModule, RouterModule]
})
export class ResumesEditorComponent {
public resume: Resume = new Resume;
public isNewResume: boolean = true;
public ErrorMsg: string = "";
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
this.title.setTitle("Resume - Editor | BoredCareers");
if (!auth.isLoggedIn){
router.navigate(["/"]);
}
this.route.queryParams.subscribe(params => {
const ResumeID = params['ResumeID'] ? +params['ResumeID'] : null;
if (ResumeID !== null){
this.http.get<Resume>("api/resume?ResumeID=" + ResumeID).subscribe({
next: data => {
this.resume = data;
this.isNewResume = false;
},
error: err => {
this.ErrorMsg = err.error;
}
});
}
});
};
SubmitForm(resume: Resume){
resume.accountID = this.auth.loggedInUser.id;
this.http.post("api/resume", resume).subscribe({
next: data => {
this.router.navigate(["/"]);
},
error: err => {
this.ErrorMsg = err.error;
}
});
}
addExperience(){
this.resume.experience.push( new ResumeExperience );
}
delExperience(self: ResumeExperience){
for(let i=0; i<this.resume.experience.length; i++){
let cur = this.resume.experience[i];
if (cur.trackUUID === self.trackUUID){
this.resume.experience.splice( i, 1 );
break;
}
}
}
addJobFunction(self: ResumeExperience){
self.experienceBullets.push( new ResumeExperienceBullet );
}
delJobFunction(self: ResumeExperience, me: ResumeExperienceBullet){
for(let i=0; i<self.experienceBullets.length; i++){
let cur = self.experienceBullets[i];
if (cur.trackUUID === me.trackUUID){
self.experienceBullets.splice( i, 1 );
break;
}
}
}
addMillitaryBullet(){
this.resume.military.millitaryBullets.push( new ResumeMilitaryBullet );
}
delMillitaryBullet(self: ResumeMilitaryBullet){
for(let i=0; i<this.resume.military.millitaryBullets.length; i++){
let cur = this.resume.military.millitaryBullets[i];
if (cur.trackUUID === self.trackUUID){
this.resume.military.millitaryBullets.splice( i, 1 );
break;
}
}
}
addEducation(){
this.resume.education.push( new ResumeEducation );
}
delEducation(self: ResumeEducation){
for(let i=0; i<this.resume.education.length; i++){
let cur = this.resume.education[i];
if (cur.trackUUID === self.trackUUID){
this.resume.education.splice( i, 1 );
break;
}
}
}
addSkill(){
this.resume.skills.push( new ResumeSkill );
}
delSkill(self: ResumeSkill){
for(let i=0; i<this.resume.skills.length; i++){
let cur = this.resume.skills[i];
if (cur.trackUUID === self.trackUUID){
this.resume.skills.splice( i, 1 );
break;
}
}
}
addLanguage(){
this.resume.languages.push( new ResumeLanguage );
}
delLanguage(self: ResumeLanguage){
for(let i=0; i<this.resume.languages.length; i++){
let cur = this.resume.languages[i];
if (cur.trackUUID === self.trackUUID){
this.resume.languages.splice( i, 1 );
break;
}
}
}
addCert(){
this.resume.certification.push( new ResumeCertification );
}
delCert(self: ResumeCertification){
for(let i=0; i<this.resume.certification.length; i++){
let cur = this.resume.certification[i];
if (cur.trackUUID === self.trackUUID){
this.resume.certification.splice( i, 1 );
break;
}
}
}
addProject(){
this.resume.projects.push( new ResumeProject );
}
delProject(self: ResumeProject){
for(let i=0; i<this.resume.projects.length; i++){
let cur = this.resume.projects[i];
if (cur.trackUUID === self.trackUUID){
this.resume.projects.splice( i, 1 );
break;
}
}
}
}
@@ -1,6 +1,11 @@
<div class="top-bar">
@for(resume of myResumes; track myResumes.length){
<button (click)="changeSelectedResume(5)">{{ resume.title }}</button>
}
<button routerLink="/resumes/editor" >NEW RESUME</button>
</div>
<!-- My Resumes -->
@if (auth.isLoggedIn){
<div class="jobs-frame">
</div>
}
@@ -15,14 +15,19 @@ import { Authentication } from 'app/services/Authentication';
})
export class ResumesComponent {
public ResumePage: Resume[] = [];
public myResumes: Resume[] = [];
public currentResume: Resume | null = null;
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
this.title.setTitle("Resumes | BoredCareers");
if (!auth.isLoggedIn){
router.navigate(["/"]);
}
this.http.get<Resume[]>("api/resume").subscribe({
next: data => {
this.ResumePage = data;
this.myResumes = data;
},
error: err => {
console.log("Error fetching resumes: " + err.error);
@@ -30,4 +35,15 @@ export class ResumesComponent {
});
};
changeSelectedResume(ResumeID: number){
this.http.get<Resume>("api/resume?ResumeID=" + ResumeID).subscribe({
next: data => {
this.currentResume = data;
},
error: err => {
console.log("Error fetching resume ID: " + err.error);
}
});
}
}
@@ -1,7 +1,7 @@
import { Injectable } from "@angular/core";
import { Account } from "../models/Account";
import { BehaviorSubject, Observable } from "rxjs";
import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http";
import { HttpClient } from "@angular/common/http";
@Injectable({ providedIn: 'root' })
export class Authentication{
+2
View File
@@ -2,6 +2,7 @@ namespace BoredCareers.Entities {
public class Resume {
public int? ID { get; set; } // PK
public int AccountID { get; set; } // FK
public string Title { get; set; } = "";
public string Name { get; set; } = "";
public string Field { get; set; } = "";
public string Email { get; set; } = "";
@@ -45,6 +46,7 @@ namespace BoredCareers.Entities {
public class ResumeMilitary {
public int? ID { get; set; } // PK
public int ResumeID { get; set; } // FK
public bool Veteran { get; set; } = false;
public string Country { get; set; } = ""; // 2 Letter Country Code
public string Rank { get; set; } = "";
public DateTime DateStarted { get; set; } = new DateTime();
+1
View File
@@ -175,6 +175,7 @@ builder.Services.AddRateLimiter(options => {
////////////////////////////////
builder.Services.AddHostedService<JobCleanupService>();
ResumeService.init();
////////////////////////////////
///// ASPNET Core Function /////
+6 -3
View File
@@ -8,15 +8,18 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.WebApiCompatShim" Version="2.3.0" />
<PackageReference Include="MySql.Data" Version="9.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Stripe.net" Version="48.2.0" />
<PackageReference Include="MySql.Data" Version="9.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="HtmlSanitizer" Version="9.0.886" />
</ItemGroup>
<ItemGroup>
@@ -27,6 +27,7 @@ namespace BoredCareers.Services.DatabaseService {
int _id = reader.GetInt32("ID");
int _accountid = reader.GetInt32("AccountID");
string _title = reader.GetString("Title");
string _name = reader.GetString("Name");
string _field = reader.GetString("Field");
string _email = reader.GetString("Email");
@@ -40,6 +41,7 @@ namespace BoredCareers.Services.DatabaseService {
resumes.Add( new Resume() {
ID = _id,
AccountID = _accountid,
Title = _title,
Name = _name,
Field = _field,
Email = _email,
@@ -10,6 +10,7 @@ namespace BoredCareers.Services.DatabaseService {
if (reader == null) { break; }
int _id = reader.GetInt32("ID");
int _accountid = reader.GetInt32("AccountID");
string _title = reader.GetString("Title");
string _name = reader.GetString("Name");
string _field = reader.GetString("Field");
string _email = reader.GetString("Email");
@@ -22,6 +23,7 @@ namespace BoredCareers.Services.DatabaseService {
return new Resume() {
ID = _id,
AccountID = _accountid,
Title = _title,
Name = _name,
Field = _field,
Email = _email,
@@ -112,6 +114,7 @@ namespace BoredCareers.Services.DatabaseService {
if (reader == null) { break; }
int _id = reader.GetInt32("ID");
int _resumeid = reader.GetInt32("ResumeID");
bool _veteran = reader.GetBoolean("Veteran");
string _country = reader.GetString("Country");
string _rank = reader.GetString("Rank");
DateTime _datestarted = reader.GetDateTime("DateStarted");
@@ -120,6 +123,7 @@ namespace BoredCareers.Services.DatabaseService {
military = new ResumeMilitary() {
ID = _id,
ResumeID = _resumeid,
Veteran = _veteran,
Country = _country,
Rank = _rank,
DateStarted = _datestarted,
@@ -7,11 +7,12 @@ namespace BoredCareers.Services.DatabaseService {
public async Task SetResume(MySqlConnection connection, Resume resume) {
string command = @"
INSERT INTO Resume
(ID,AccountID,Name,Field,Email,PhoneNumber,PostalCode,Country,StateOrRegion,City,IsActive)
(ID,AccountID,Title,Name,Field,Email,PhoneNumber,PostalCode,Country,StateOrRegion,City,IsActive)
VALUES
(@ID,@AccountID,@Name,@Field,@Email,@PhoneNumber,@PostalCode,@Country,@StateOrRegion,@City,@IsActive)
(@ID,@AccountID,@Title,@Name,@Field,@Email,@PhoneNumber,@PostalCode,@Country,@StateOrRegion,@City,@IsActive)
ON DUPLICATE KEY UPDATE
AccountID = @AccountID,
Title = @Title,
Name = @Name,
Field = @Field,
Email = @Email,
@@ -26,6 +27,7 @@ namespace BoredCareers.Services.DatabaseService {
MySqlCommand cmd = new MySqlCommand(command, connection);
cmd.Parameters.AddWithValue("@ID", resume.ID);
cmd.Parameters.AddWithValue("@AccountID", resume.AccountID);
cmd.Parameters.AddWithValue("@Title", resume.Title);
cmd.Parameters.AddWithValue("@Name", resume.Name);
cmd.Parameters.AddWithValue("@Field", resume.Field);
cmd.Parameters.AddWithValue("@Email", resume.Email);
@@ -124,11 +126,12 @@ namespace BoredCareers.Services.DatabaseService {
public async Task SetResumeMilitary(MySqlConnection connection, ResumeMilitary military) {
string command = @"
INSERT INTO Resume
(ID,ResumeID,Country,Rank,DateStarted,StillServing,DateEnded)
(ID,ResumeID,Veteran,Country,Rank,DateStarted,StillServing,DateEnded)
VALUES
(@ID,@ResumeID,@Country,@Rank,@DateStarted,@StillServing,@DateEnded)
(@ID,@ResumeID,@Veteran,@Country,@Rank,@DateStarted,@StillServing,@DateEnded)
ON DUPLICATE KEY UPDATE
ResumeID = @ResumeID,
Veteran = @Veteran,
Country = @Country,
Rank = @Rank,
DateStarted = @DateStarted,
@@ -139,6 +142,7 @@ namespace BoredCareers.Services.DatabaseService {
MySqlCommand cmd = new MySqlCommand(command, connection);
cmd.Parameters.AddWithValue("@ID", military.ID);
cmd.Parameters.AddWithValue("@ResumeID", military.ResumeID);
cmd.Parameters.AddWithValue("@Veteran", military.Veteran);
cmd.Parameters.AddWithValue("@Country", military.Country);
cmd.Parameters.AddWithValue("@Rank", military.Rank);
cmd.Parameters.AddWithValue("@DateStarted", military.DateStarted.ToUniversalTime());
+93
View File
@@ -0,0 +1,93 @@
using Ganss.Xss;
namespace BoredCareers.Services {
public class ResumeService {
static HtmlSanitizer _self = new HtmlSanitizer();
public static void init() {
// Clear default allowed tags and attributes
_self.AllowedAttributes.Clear();
_self.AllowedSchemes.Clear();
_self.AllowedAtRules.Clear();
_self.AllowedClasses.Clear();
// Allowed HTML Tags
_self.AllowedTags.Clear();
string[] safeTags = [
"b", "strong", "i", "em", "u", "small", "mark", "del", "ins", "sub", "sup",
"p", "br", "hr", "div", "span",
"section", "article", "header", "footer", "aside", "main", "nav",
"ul", "ol", "li", "dl", "dt", "dd",
"h1", "h2", "h3", "h4", "h5", "h6",
"blockquote", "q", "cite",
"code", "pre", "samp", "kbd", "var",
"table", "thead", "tbody", "tfoot", "tr", "td", "th",
];
foreach (string cur in safeTags) {
_self.AllowedTags.Add(cur);
}
// Allow inline styles only
_self.AllowedAttributes.Add("style");
string[] safeCssProperties = [
"align-content", "align-items", "align-self", "all",
"animation", "animation-delay", "animation-direction", "animation-duration",
"animation-fill-mode", "animation-iteration-count", "animation-name", "animation-play-state",
"animation-timing-function", "backface-visibility", "background-color", "background-clip",
"background-origin", "background-position", "background-repeat", "background-size",
"border", "border-bottom", "border-bottom-color", "border-bottom-left-radius",
"border-bottom-right-radius", "border-bottom-style", "border-bottom-width", "border-color",
"border-image-outset", "border-image-repeat", "border-image-slice", "border-image-source",
"border-image-width", "border-left", "border-left-color", "border-left-style",
"border-left-width", "border-radius", "border-right", "border-right-color",
"border-right-style", "border-right-width", "border-spacing", "border-style",
"border-top", "border-top-color", "border-top-left-radius", "border-top-right-radius",
"border-top-style", "border-top-width", "border-width", "bottom",
"box-decoration-break", "box-shadow", "box-sizing", "caption-side",
"clear", "color", "column-count", "column-fill",
"column-gap", "column-rule-color", "column-rule-style", "column-rule-width",
"column-span", "column-width", "columns", "counter-increment",
"counter-reset", "direction", "display", "empty-cells",
"flex", "flex-basis", "flex-direction", "flex-flow",
"flex-grow", "flex-shrink", "flex-wrap", "float",
"font-family", "font-feature-settings", "font-kerning", "font-language-override",
"font-size", "font-size-adjust", "font-stretch", "font-style",
"font-synthesis", "font-variant", "font-variant-alternates", "font-variant-caps",
"font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric", "font-variant-position",
"font-weight", "grid", "grid-area", "grid-auto-columns",
"grid-auto-flow", "grid-auto-rows", "grid-column", "grid-column-end",
"grid-column-gap", "grid-column-start", "grid-gap", "grid-row",
"grid-row-end", "grid-row-gap", "grid-row-start", "grid-template",
"grid-template-areas", "grid-template-columns", "grid-template-rows", "height",
"hyphens", "image-rendering", "isolation", "justify-content",
"left", "letter-spacing", "line-height", "list-style-position",
"list-style-type", "margin", "margin-bottom", "margin-left",
"margin-right", "margin-top", "max-height", "max-width",
"min-height", "min-width", "object-fit", "object-position",
"opacity", "order", "orphans", "outline-color",
"outline-offset", "outline-style", "outline-width", "overflow",
"overflow-wrap", "overflow-x", "overflow-y", "padding",
"padding-bottom", "padding-left", "padding-right", "padding-top",
"page-break-after", "page-break-before", "page-break-inside", "perspective",
"perspective-origin", "pointer-events", "position", "quotes",
"resize", "right", "scroll-behavior", "table-layout",
"tab-size", "text-align", "text-align-last", "text-combine-upright",
"text-indent", "text-justify", "text-orientation", "text-overflow",
"text-shadow", "text-transform", "text-underline-position", "top",
"transform", "transform-origin", "transform-style", "transition",
"transition-delay", "transition-duration", "transition-property", "transition-timing-function",
"unicode-bidi", "user-select", "vertical-align", "visibility",
"white-space", "widows", "width", "word-break",
"word-spacing", "word-wrap", "writing-mode", "z-index"
];
foreach (string cur in safeCssProperties) {
_self.AllowedCssProperties.Add(cur);
}
}
public static string RemoveJavascript(string InputHTML) {
return _self.Sanitize(InputHTML);
}
}
}