From f1279ce38a4c3056d03ec6009bf432933329214b Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 19:08:58 -0700 Subject: [PATCH 01/12] Merged into company page --- .../main/company/jobs/jobs.component.css | 87 ------------------- .../main/company/jobs/jobs.component.html | 23 ----- .../pages/main/company/jobs/jobs.component.ts | 52 ----------- 3 files changed, 162 deletions(-) delete mode 100644 src/Client/src/app/pages/main/company/jobs/jobs.component.css delete mode 100644 src/Client/src/app/pages/main/company/jobs/jobs.component.html delete mode 100644 src/Client/src/app/pages/main/company/jobs/jobs.component.ts diff --git a/src/Client/src/app/pages/main/company/jobs/jobs.component.css b/src/Client/src/app/pages/main/company/jobs/jobs.component.css deleted file mode 100644 index 6044df9..0000000 --- a/src/Client/src/app/pages/main/company/jobs/jobs.component.css +++ /dev/null @@ -1,87 +0,0 @@ -button { - width: 150px; - border-radius: 5px; - margin: 10px; - text-align: center; - padding: 15px 0; - transition: .5s; - background-color: #0000; - border: 1px solid var(--Mistox-White); - color: var(--Mistox-White); - text-decoration: none; -} - - button:hover { - background-color: #00000044; - color: var(--Mistox-Light); - } - -.full-width { - display: block; - width: 100%; - column-count: 2; -} - -.tile-frame { - display: grid; - grid-template-columns: repeat(4, 1fr); - column-gap: 20px; - padding: 20px; - width: calc(100% - 40px); -} - -.tile{ - background-color: var(--Mistox-Dark); - color: var(--Mistox-White); - break-inside: avoid; - padding: 20px; - border-radius: 20px; - margin-bottom: 20px; -} - -.jobs-frame { - width: 100%; - background-color: #8888; - border-top: 2px solid black; -} - -.post-job-frame { - display: flex; - justify-content: center; - padding: 10px 0; -} - -.tile-title { - text-align: center; - border-bottom: 1px solid; -} - -.tile-title h1 { - font-size: 40px; - margin: 5px 0; -} - -.tile-title h2 { - font-size: 14px; -} - -.tile-split { - columns: 2; - text-align: center; - padding: 10px 0; -} - -.tile-split h1 { - margin: 0; -} - -.tile-button { - display: flex; - width: 100%; - justify-content: center; -} - -.post-job-frame button { - border-color: var(--Mistox-Black); - color: var(--Mistox-Black); -} \ No newline at end of file diff --git a/src/Client/src/app/pages/main/company/jobs/jobs.component.html b/src/Client/src/app/pages/main/company/jobs/jobs.component.html deleted file mode 100644 index db11b14..0000000 --- a/src/Client/src/app/pages/main/company/jobs/jobs.component.html +++ /dev/null @@ -1,23 +0,0 @@ -
- -
- -
-
-
-

{{ cur.title }}

-

{{ cur.jobType }}

-

Is Remote: {{ cur.remote }}

-

{{ cur.salaryMin }}

-

{{ cur.salaryMax }}

-

{{ cur.city }}

-

{{ cur.stateOrRegion }}

-

{{ cur.country }}

-

{{ cur.postalCode }}

-

Posted: {{ cur.createdTime }}

-

Modified: {{ cur.modifiedTime }}

-
- - -
-
\ No newline at end of file diff --git a/src/Client/src/app/pages/main/company/jobs/jobs.component.ts b/src/Client/src/app/pages/main/company/jobs/jobs.component.ts deleted file mode 100644 index d81f192..0000000 --- a/src/Client/src/app/pages/main/company/jobs/jobs.component.ts +++ /dev/null @@ -1,52 +0,0 @@ -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 { JobListing } from 'app/models/JobListing'; -import { Authentication } from 'app/services/Authentication'; - -@Component({ - selector: 'main-company-jobs', - templateUrl: './jobs.component.html', - styleUrls: [ './jobs.component.css' ], - imports: [ FormsModule, CommonModule, RouterModule ] -}) -export class CompanyJobsComponent { - - public MyJobListings: JobListing[] = []; - public ErrorMsg: string = ""; - - constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) { - this.title.setTitle("Company - Jobs | BoredCareers"); - - this.route.queryParams.subscribe(params => { - const companyID = params['CompanyID']; - if (companyID){ - http.get("api/joblisting/company?CompanyID=" + companyID).subscribe({ - next: data => { - this.MyJobListings = data; - }, - error: err => { - this.ErrorMsg = err.error; - } - }); - }else{ - router.navigate(["/company"]); - } - }); - }; - - RemoveJobListing( JobListingID: number ){ - this.http.delete("api/joblisting?JobListingID=" + JobListingID).subscribe({ - next: data => { - window.location.reload(); - }, - error: err => { - this.ErrorMsg = err.error; - } - }); - } - -} \ No newline at end of file From 1503ed8c5c6d31af0326359b722431c40c071c4b Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 19:09:16 -0700 Subject: [PATCH 02/12] Correct salary direction in UI --- src/Client/src/app/pages/main/jobs/jobs.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/src/app/pages/main/jobs/jobs.component.html b/src/Client/src/app/pages/main/jobs/jobs.component.html index f6bdacc..a92934d 100644 --- a/src/Client/src/app/pages/main/jobs/jobs.component.html +++ b/src/Client/src/app/pages/main/jobs/jobs.component.html @@ -3,7 +3,7 @@

{{ cur.title }}

-

${{ cur.salaryMax }} - ${{ cur.salaryMin }}

+

${{ cur.salaryMin }} - ${{ cur.salaryMax }}

{{ cur.jobType }}

From cf5dc462f595629fee25ee6bf734a436d1aae4a1 Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 19:09:32 -0700 Subject: [PATCH 03/12] Only show jobs if they arent deleted --- src/Server/Services/DatabaseService/JobListing.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Server/Services/DatabaseService/JobListing.cs b/src/Server/Services/DatabaseService/JobListing.cs index 18fa04a..f23b28a 100644 --- a/src/Server/Services/DatabaseService/JobListing.cs +++ b/src/Server/Services/DatabaseService/JobListing.cs @@ -73,7 +73,8 @@ namespace BoredCareers.Services.DatabaseService { string command = @" SELECT * FROM JobListing - WHERE CompanyID = @CompanyID; + WHERE IsDeleted = FALSE + AND CompanyID = @CompanyID; "; MySqlCommand cmd = new MySqlCommand(command, connection); From b4b85ec238f117ae8d47eee92085b0bfe6322180 Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 19:10:09 -0700 Subject: [PATCH 04/12] Allow new/edit on same page & infer company --- .../main/jobs/editor/jobeditor.component.html | 43 ++++-------- .../main/jobs/editor/jobeditor.component.ts | 66 ++++++++++++++----- 2 files changed, 62 insertions(+), 47 deletions(-) diff --git a/src/Client/src/app/pages/main/jobs/editor/jobeditor.component.html b/src/Client/src/app/pages/main/jobs/editor/jobeditor.component.html index c519c73..e60b47f 100644 --- a/src/Client/src/app/pages/main/jobs/editor/jobeditor.component.html +++ b/src/Client/src/app/pages/main/jobs/editor/jobeditor.component.html @@ -1,33 +1,14 @@

POST A NEW JOB

-
- - -
-
-
- - - -
- -
-
+
- +
@@ -40,7 +21,7 @@
- @@ -50,7 +31,7 @@
- +
@@ -59,28 +40,28 @@
-
+

Job Location

- +
- +
- +
- +
@@ -96,11 +77,11 @@
- +
- +
@@ -113,7 +94,7 @@
- +
diff --git a/src/Client/src/app/pages/main/jobs/editor/jobeditor.component.ts b/src/Client/src/app/pages/main/jobs/editor/jobeditor.component.ts index 63b61bc..b5522dc 100644 --- a/src/Client/src/app/pages/main/jobs/editor/jobeditor.component.ts +++ b/src/Client/src/app/pages/main/jobs/editor/jobeditor.component.ts @@ -15,31 +15,46 @@ import { Company, Employee } from 'app/models/Company'; imports: [ FormsModule, CommonModule, RouterModule ] }) export class JobEditorComponent { + public ErrorMsg: string = ""; @ViewChildren('step') formSteps!: QueryList>; currentStep: number = 0; - public employeeOfList: Employee[] = []; - public selectedCompany: Company = new Company; + public Listing: JobListing = new JobListing(); - public newListing: JobListing = new JobListing(); - public ErrorMsg: string = ""; + public mode: string = ""; + public modeID: number = 0; constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) { this.title.setTitle("Jobs - Editor | BoredCareers"); - this.http.get("api/employee").subscribe({ - next: empOf => { - if (empOf.length === 0){ - router.navigate(["company/connect"]); - } - this.employeeOfList = empOf; - }, - error: err => { - this.ErrorMsg = err.error; + this.route.queryParams.subscribe(params => { + const CompanyID = params['CompanyID'] ? +params['CompanyID'] : null; + const JobID = params['JobID'] ? +params['JobID'] : null; + if (CompanyID !== null && JobID !== null){ + this.router.navigate([""]); + }else if (CompanyID !== null ){ + this.mode = "new"; + this.modeID = CompanyID; + }else if(JobID !== null){ + this.mode = "edit"; + this.modeID = JobID; + }else if (CompanyID === null && JobID === null){ + this.router.navigate([""]); + } + + if (this.mode === "edit") { + this.http.get("api/joblisting/" + JobID).subscribe({ + next: data => { + this.Listing = data; + }, + error: err => { + this.ErrorMsg = err.error; + } + }); } }); - }; + } ngAfterViewInit(){ this.formSteps.changes.subscribe(() => { @@ -70,8 +85,8 @@ export class JobEditorComponent { this.updateUI(); } - PostJobListing(jobListing: JobListing){ - jobListing.companyID = this.selectedCompany.id!; + PostNewJob(jobListing: JobListing){ + jobListing.companyID = this.modeID; this.http.post("api/joblisting", jobListing).subscribe({ next: data => { this.router.navigate([""]); @@ -82,4 +97,23 @@ export class JobEditorComponent { }); } + PostEditJob(jobListing: JobListing){ + this.http.post("api/joblisting", jobListing).subscribe({ + next: data => { + this.router.navigate([""]); + }, + error: err => { + this.ErrorMsg = err.error; + } + }); + } + + SubmitForm(job: JobListing){ + if (this.mode === "new"){ + this.PostNewJob(job); + }else if (this.mode === "edit"){ + this.PostEditJob(job); + } + } + } \ No newline at end of file From ce6c57f00d85fb9c04deb6b4141b08ad9b6f318a Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 19:10:44 -0700 Subject: [PATCH 05/12] rename companyconnect to fit with URL scheme --- .../editor.component.css} | 0 .../editor.component.html} | 0 .../editor.component.ts} | 15 +++++++++------ 3 files changed, 9 insertions(+), 6 deletions(-) rename src/Client/src/app/pages/main/company/{connect/companyconnect.component.css => editor/editor.component.css} (100%) rename src/Client/src/app/pages/main/company/{connect/companyconnect.component.html => editor/editor.component.html} (100%) rename src/Client/src/app/pages/main/company/{connect/companyconnect.component.ts => editor/editor.component.ts} (92%) diff --git a/src/Client/src/app/pages/main/company/connect/companyconnect.component.css b/src/Client/src/app/pages/main/company/editor/editor.component.css similarity index 100% rename from src/Client/src/app/pages/main/company/connect/companyconnect.component.css rename to src/Client/src/app/pages/main/company/editor/editor.component.css diff --git a/src/Client/src/app/pages/main/company/connect/companyconnect.component.html b/src/Client/src/app/pages/main/company/editor/editor.component.html similarity index 100% rename from src/Client/src/app/pages/main/company/connect/companyconnect.component.html rename to src/Client/src/app/pages/main/company/editor/editor.component.html diff --git a/src/Client/src/app/pages/main/company/connect/companyconnect.component.ts b/src/Client/src/app/pages/main/company/editor/editor.component.ts similarity index 92% rename from src/Client/src/app/pages/main/company/connect/companyconnect.component.ts rename to src/Client/src/app/pages/main/company/editor/editor.component.ts index 11be3da..3c21b7c 100644 --- a/src/Client/src/app/pages/main/company/connect/companyconnect.component.ts +++ b/src/Client/src/app/pages/main/company/editor/editor.component.ts @@ -8,12 +8,12 @@ import { Authentication } from 'app/services/Authentication'; import { Company } from 'app/models/Company'; @Component({ - selector: 'main-company-connect', - templateUrl: './companyconnect.component.html', - styleUrls: [ './companyconnect.component.css' ], + selector: 'main-company-editor', + templateUrl: './editor.component.html', + styleUrls: [ './editor.component.css' ], imports: [ FormsModule, CommonModule, RouterModule ] }) -export class CompanyConnectComponent { +export class CompanyEditorComponent { @ViewChildren('step') formSteps!: QueryList>; currentStep: number = 0; @@ -23,7 +23,10 @@ export class CompanyConnectComponent { MaxFileMB: number = 3; constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) { - this.title.setTitle("Company - Connect | BoredCareers"); + this.title.setTitle("Company - Editor | BoredCareers"); + + // Query param CompanyID -> Edit + // Query param null -> New }; ngAfterViewInit(){ @@ -35,7 +38,7 @@ export class CompanyConnectComponent { @HostListener('window:keydown', ['$event']) handleGlobalKeyDown(event: KeyboardEvent){ - if (event.key === 'Tab'){ + if ( event.key === 'Tab' ){ event.preventDefault(); } } From 134750447c9366876501f68235b10c4afbcb323e Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 19:12:07 -0700 Subject: [PATCH 06/12] Build company page out --- .../pages/main/company/company.component.css | 91 +++++++++++++++++++ .../pages/main/company/company.component.html | 53 ++++++++--- .../pages/main/company/company.component.ts | 31 +++++++ 3 files changed, 161 insertions(+), 14 deletions(-) diff --git a/src/Client/src/app/pages/main/company/company.component.css b/src/Client/src/app/pages/main/company/company.component.css index 290016c..1ceeb47 100644 --- a/src/Client/src/app/pages/main/company/company.component.css +++ b/src/Client/src/app/pages/main/company/company.component.css @@ -39,6 +39,97 @@ button { justify-content: center; } +.content-edit { + position: absolute; + right: 20px; +} + .center-item img { width: 300px; +} + +.content-name { + width: 300px; + text-align: center; + font-size: 30px; +} + +.content-name h1 { + margin: 0; +} + +.content-link { + display: flex; + width: 300px; + justify-content: center; +} + +.content-link a { + text-decoration: none; + color: var(--Mistox-White); + margin-top: auto; +} + +.content-desc { + border: solid 1px red; + border-radius: 5px; + padding: 20px; + margin: 0 100px; + margin-bottom: 0px; + margin-bottom: 50px; +} + +.content-desc h1 { + margin: 0; + font-size: 20px; + color: #ddd; +} + +.content-button { + display: flex; + justify-content: center; +} + +.content-button span { + align-content: center; +} + +.split-frame { + display: flex; + width: 100%; +} + +.half-frame { + width: 50%; + border-right: solid 1px var(--Mistox-Black); + border-left: solid 1px var(--Mistox-Black); +} + +.half-frame h2 { + text-align: center; +} + +.job-tile { + display: flex; + background-color: var(--Mistox-Black); + justify-content: end; + align-items: center; + border-radius: 10px; + margin: 0 5px; + margin-bottom: 10px; +} + +.center-text { + display: flex; + flex: 1; + justify-content: center; +} + +.job-tile h1 { + margin: 0; +} + +.job-tile button { + color: white; + border-color: white; } \ No newline at end of file diff --git a/src/Client/src/app/pages/main/company/company.component.html b/src/Client/src/app/pages/main/company/company.component.html index 204c13b..90f926c 100644 --- a/src/Client/src/app/pages/main/company/company.component.html +++ b/src/Client/src/app/pages/main/company/company.component.html @@ -1,25 +1,50 @@
- +
+
- + +

{{ Comp.name }}

+ +
+
+

{{ Comp.city }}, {{ Comp.stateOrRegion }} {{ Comp.postalCode }}

+
+
+

{{ line }}

+
+
+ +
+
+ + You must verify your company email before you can post job listings. +
+
+
+
+

Active Job Listings

+
+
+

{{ listing.title }}

+
+ + + +
+
+
+

Employees

+ +
-

{{ Comp.emailVerified }}

- -

{{ Comp.phone }}

-

{{ Comp.postalCode }}

-

{{ Comp.country }}

-

{{ Comp.stateOrRegion }}

-

{{ Comp.city }}

-

{{ Comp.description }}

-
\ No newline at end of file diff --git a/src/Client/src/app/pages/main/company/company.component.ts b/src/Client/src/app/pages/main/company/company.component.ts index dc8ee16..015c90d 100644 --- a/src/Client/src/app/pages/main/company/company.component.ts +++ b/src/Client/src/app/pages/main/company/company.component.ts @@ -6,6 +6,7 @@ import { Title } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; import { Authentication } from 'app/services/Authentication'; import { Company, Employee } from 'app/models/Company'; +import { JobListing } from 'app/models/JobListing'; @Component({ selector: 'main-company', @@ -17,13 +18,22 @@ export class CompanyComponent { public ErrorMsg: string = ""; public Employers: Employee[] = []; + public Comp: Company | null = null; + public Desc: string[] = []; + + public List: JobListing[] = []; constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) { this.title.setTitle("Companies | BoredCareers"); http.get("api/employee/").subscribe({ next: data => { this.Employers = data; + if (data[0] != null){ + if (data[0].company.id !== null){ + this.changeSelectedCompany(data[0].company.id); + } + } }, error: err => { this.ErrorMsg = err.error; @@ -36,6 +46,27 @@ export class CompanyComponent { this.http.get("api/company?CompanyID=" + companyID).subscribe({ next: data => { this.Comp = data; + this.Desc = data.description.split("\n"); + }, + error: err => { + this.ErrorMsg = err.error; + } + }); + + this.http.get("api/joblisting/company?CompanyID=" + companyID).subscribe({ + next: data => { + this.List = data; + }, + error: err => { + this.ErrorMsg = err.error; + } + }); + } + + RemoveJobListing( JobListingID: number ){ + this.http.delete("api/joblisting?JobListingID=" + JobListingID).subscribe({ + next: data => { + window.location.reload(); }, error: err => { this.ErrorMsg = err.error; From 5fff17f1e0011802c7b51c084c1eae2b2d3cc1ae Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 19:12:40 -0700 Subject: [PATCH 07/12] Fix navbar highlighting --- src/Client/src/app/app.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Client/src/app/app.ts b/src/Client/src/app/app.ts index 7c72022..147e71a 100644 --- a/src/Client/src/app/app.ts +++ b/src/Client/src/app/app.ts @@ -13,10 +13,6 @@ import { isDevMode } from '@angular/core'; }) export class App { - @ViewChild('companiesLink') companiesLink!: ElementRef; - @ViewChild('jobsLink') jobLink!: ElementRef; - @ViewChild('resumesLink') resumeLink!: ElementRef; - devMode: boolean = false; constructor( private http: HttpClient, public auth: Authentication, private router: Router, private route: ActivatedRoute, private location: Location){ @@ -48,13 +44,4 @@ export class App { }); } - ngAfterViewInit(){ - let ViewLinks = [ this.companiesLink, this.resumeLink, this.jobLink ]; - ViewLinks.forEach(link => { - if (new URL(link.nativeElement.href).pathname === new URL(window.location.href).pathname){ - link.nativeElement.classList.add("active"); - } - }); - } - } From 292b50be02a4a47d8d42bcbd665c94ffdfc324a1 Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 19:12:58 -0700 Subject: [PATCH 08/12] Update routes for all the changes --- ToDo.yaml | 21 ++++++++++++--------- src/Client/src/app/app.html | 6 +++--- src/Client/src/app/app.routes.ts | 7 +++---- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/ToDo.yaml b/ToDo.yaml index 3f213f4..5b512f4 100755 --- a/ToDo.yaml +++ b/ToDo.yaml @@ -18,15 +18,10 @@ Server: Need to update notification email Client: - jobs/new: + jobs/editor: 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 ] @@ -38,9 +33,17 @@ Client: 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 - + company/editor: + Need to lookup company before making a new one + Tab key does nothing + Format phone number for database + Check DataType's for email and phone. + Setup QueryParam's for Edit and New + Edit employees not implimented yet + + Company: + No employees for table yet + database: Add Applied Jobs Table \ No newline at end of file diff --git a/src/Client/src/app/app.html b/src/Client/src/app/app.html index 369369b..c02f3a9 100644 --- a/src/Client/src/app/app.html +++ b/src/Client/src/app/app.html @@ -1,8 +1,8 @@
+
+

Opened: {{ selectedJob.createdTime }}

+

Modified: {{ selectedJob.modifiedTime }}

+

THIS JOB POSTING IS CLOSED

@@ -35,8 +41,5 @@

{{ selectedJob.postalCode }}

{{ selectedJob.description }}

- -

{{ selectedJob.createdTime }}

-

{{ selectedJob.modifiedTime }}

\ No newline at end of file From eb27dfd6bf05c9c118fded3513d9681a8b53a4c9 Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 20:45:47 -0700 Subject: [PATCH 10/12] Add Email Token to company --- database/mistox.sql | 1 + src/Server/Controllers/CompanyController.cs | 1 + src/Server/Entities/Company.cs | 1 + src/Server/Services/DatabaseService/Company.cs | 8 ++++++-- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/database/mistox.sql b/database/mistox.sql index 72c979b..bcf7be0 100755 --- a/database/mistox.sql +++ b/database/mistox.sql @@ -129,6 +129,7 @@ CREATE TABLE IF NOT EXISTS `Company` ( `Name` varchar(100) DEFAULT NULL, `Email` varchar(255) DEFAULT NULL, `EmailVerified` boolean DEFAULT 0, + `EmailToken` char(36) DEFAULT NULL, `WebsiteURL` varchar(255) DEFAULT NULL, `Logo` mediumblob DEFAULT NULL, `JobsClosedSuccessful` int DEFAULT 0, diff --git a/src/Server/Controllers/CompanyController.cs b/src/Server/Controllers/CompanyController.cs index c546bf8..695b90a 100644 --- a/src/Server/Controllers/CompanyController.cs +++ b/src/Server/Controllers/CompanyController.cs @@ -15,6 +15,7 @@ namespace BoredCareers.Controllers { if (isLoggedIn()) { Company? company = await _databaseService.GetCompany(CompanyID); if (company != null) { + company.EmailToken = ""; return Ok(company); } return NotFound("Company doesn't exist"); diff --git a/src/Server/Entities/Company.cs b/src/Server/Entities/Company.cs index c6fbcb8..f30d2e1 100644 --- a/src/Server/Entities/Company.cs +++ b/src/Server/Entities/Company.cs @@ -5,6 +5,7 @@ namespace BoredCareers.Entities { public string Name { get; set; } = ""; public string Email { get; set; } = ""; public bool EmailVerified { get; set; } = false; + public string EmailToken { get; set; } = ""; public string WebsiteURL { get; set; } = ""; public string Logo { get; set; } = ""; public int JobsClosedSuccessful { get; set; } diff --git a/src/Server/Services/DatabaseService/Company.cs b/src/Server/Services/DatabaseService/Company.cs index 5aaad81..5e6d334 100644 --- a/src/Server/Services/DatabaseService/Company.cs +++ b/src/Server/Services/DatabaseService/Company.cs @@ -26,6 +26,7 @@ namespace BoredCareers.Services.DatabaseService { string _name = reader.GetString("Name"); string _email = reader.GetString("Email"); bool _emailVerified = reader.GetBoolean("EmailVerified"); + string _emailtoken = reader.GetString("EmailToken"); string _websiteurl = reader.GetString("WebsiteURL"); string _logo = Encoding.UTF8.GetString((byte[])reader["Logo"]); int _jobsclosedsuccessful = reader.GetInt32("JobsClosedSuccessful"); @@ -42,6 +43,7 @@ namespace BoredCareers.Services.DatabaseService { Name = _name, Email = _email, EmailVerified = _emailVerified, + EmailToken = _emailtoken, WebsiteURL = _websiteurl, Logo = _logo, JobsAutoClosed = _jobsautoclosed, @@ -64,13 +66,14 @@ namespace BoredCareers.Services.DatabaseService { await connection.OpenAsync(); string command = @" INSERT INTO Company - (ID,Name,Email,EmailVerified,WebsiteURL,Logo,JobsClosedSuccessful,JobsAutoClosed,Phone,PostalCode,Country,StateOrRegion,City,Description) + (ID,Name,Email,EmailVerified,EmailToken,WebsiteURL,Logo,JobsClosedSuccessful,JobsAutoClosed,Phone,PostalCode,Country,StateOrRegion,City,Description) VALUES - (@ID,@Name,@Email,@EmailVerified,@WebsiteURL,@Logo,@JobsClosedSuccessful,@JobsAutoClosed,@Phone,@PostalCode,@Country,@StateOrRegion,@City,@Description) + (@ID,@Name,@Email,@EmailVerified,@EmailToken,@WebsiteURL,@Logo,@JobsClosedSuccessful,@JobsAutoClosed,@Phone,@PostalCode,@Country,@StateOrRegion,@City,@Description) ON DUPLICATE KEY UPDATE Name = @Name, Email = @Email, EmailVerified = @EmailVerified, + EmailToken = @EmailToken, WebsiteURL = @WebsiteURL, Logo = @Logo, JobsClosedSuccessful = @JobsClosedSuccessful, @@ -90,6 +93,7 @@ namespace BoredCareers.Services.DatabaseService { cmd.Parameters.AddWithValue("@Name", company.Name); cmd.Parameters.AddWithValue("@Email", company.Email); cmd.Parameters.AddWithValue("@EmailVerified", company.EmailVerified); + cmd.Parameters.AddWithValue("@EmailToken", company.EmailToken); cmd.Parameters.AddWithValue("@WebsiteURL", company.WebsiteURL); cmd.Parameters.AddWithValue("@Logo", Encoding.UTF8.GetBytes(company.Logo)); cmd.Parameters.AddWithValue("@JobsClosedSuccessful", company.JobsClosedSuccessful); From d8cd8d4a57e99f8ec5351ba9f11c000ce904e58a Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 21:04:29 -0700 Subject: [PATCH 11/12] Add in verify company email --- src/Server/Controllers/CompanyController.cs | 60 ++++++++++++++++++- .../EmailService/CompanyVerifyEmail.cs | 52 ++++++++++++++++ .../EmailService/JobAutoCloseEmail.cs | 2 +- 3 files changed, 111 insertions(+), 3 deletions(-) create mode 100755 src/Server/Services/EmailService/CompanyVerifyEmail.cs diff --git a/src/Server/Controllers/CompanyController.cs b/src/Server/Controllers/CompanyController.cs index 695b90a..d27bb70 100644 --- a/src/Server/Controllers/CompanyController.cs +++ b/src/Server/Controllers/CompanyController.cs @@ -2,13 +2,18 @@ using Microsoft.AspNetCore.Mvc; using BoredCareers.Services.DatabaseService; using BoredCareers.Entities; using System.Web.Http; +using BoredCareers.Services; namespace BoredCareers.Controllers { [ApiController] [Route("api/company")] public class CompanyController : MistoxControllerBase { - public CompanyController(DatabaseService db) : base(db) {} + EmailService _emailContext; + + public CompanyController(DatabaseService db, EmailService emailContext) : base(db) { + _emailContext = emailContext; + } [HttpGet] public async Task GetCompany(int CompanyID) { @@ -60,6 +65,57 @@ namespace BoredCareers.Controllers { return NotFound("Not logged in"); } - } + [HttpGet("sendverifyemail")] + public async Task> SendVerify([FromQuery] int CompanyID) { + try { + string key = "v" + CompanyID; + // Stop from sending multiple emails quickly + if (_emailContext._SentEmails.ContainsKey(key)) { + DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key); + if (PreviousSentTime.AddMinutes(5) > DateTime.Now) { + return NotFound("Cannot sent another verify email until 5 minutes has elapsed"); + } else { + _emailContext._SentEmails.Remove(key); + } + } + Company? test = await _databaseService.GetCompany(CompanyID); + if (test != null) { + test.EmailToken = Guid.NewGuid().ToString(); + await _databaseService.SetCompany(test); + string EmailContents = EmailService.CompanyVerifyEmailSubject; + EmailContents = Substitue(EmailContents, "@CompanyName", test.Name); + EmailContents = Substitue(EmailContents, "@ID", CompanyID.ToString()); + EmailContents = Substitue(EmailContents, "@VerifyPassword", test.EmailToken); + + string result = _emailContext.Send(test.Email, EmailService.CompanyVerifyEmailSubject, EmailContents); + _emailContext._SentEmails.Add(key, DateTime.Now); + return Redirect("/"); + } + return NotFound("Account not found"); + } catch (Exception) { + return NotFound("An internal server error has occured"); + } + } + + [HttpGet("verifyemail")] + public async Task> VerifyEmail([FromQuery] int CompanyID, [FromQuery] string EmailToken) { + try { + Company? test = await _databaseService.GetCompany(CompanyID); + if (test != null) { + if (test.EmailToken == EmailToken) { + test.EmailToken = ""; + test.EmailVerified = true; + await _databaseService.SetCompany(test); + return Redirect("/"); + } + return BadRequest("The token isn't valid"); + } + return BadRequest("Account not found"); ; + } catch { + return BadRequest("An internal server error has occured"); + } + } + + } } diff --git a/src/Server/Services/EmailService/CompanyVerifyEmail.cs b/src/Server/Services/EmailService/CompanyVerifyEmail.cs new file mode 100755 index 0000000..ccaecca --- /dev/null +++ b/src/Server/Services/EmailService/CompanyVerifyEmail.cs @@ -0,0 +1,52 @@ +namespace BoredCareers.Services { + public partial class EmailService { + +// @UserName +// @VerifyPassword +// https://mistox.com/api/account/verifyemail?UserName=@UserName&Guid=@VerifyPassword + + public static string CompanyVerifyEmailSubject = "Verify Your Email Address"; + public static string CompanyVerifyEmailBody = @" + + + + + + Verify Your Email + + + + + + +
+ + + + + + + + + + +
+

Verify Email Request

+
+

Hi @CompanyName,

+

Thank you for making an account with us:

+

In order to start using your account we need to verify your email address by clicking the link below:

+

+ Verify Email +

+

If you didn't create an account please ignore this email.

+

Best regards

+
+

If you have any questions, feel free to contact support.

+
+
+ +"; + + } +} \ No newline at end of file diff --git a/src/Server/Services/EmailService/JobAutoCloseEmail.cs b/src/Server/Services/EmailService/JobAutoCloseEmail.cs index a1055f3..00aeb87 100755 --- a/src/Server/Services/EmailService/JobAutoCloseEmail.cs +++ b/src/Server/Services/EmailService/JobAutoCloseEmail.cs @@ -6,7 +6,7 @@ namespace BoredCareers.Services { // https://mistox.com/api/account/verifyemail?UserName=@UserName&Guid=@VerifyPassword public static string JobAutoClosedSubject = "Verify Your Email Address"; - public static string JobAutoClosedEmail = @" + public static string JobAutoClosedBody = @" From 5c721c22e298f2bfe85f86bb165a30503c6c5913 Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 5 Aug 2025 21:04:37 -0700 Subject: [PATCH 12/12] Start work for auto close notify --- ToDo.yaml | 3 +++ .../BackgroundServices/JobCleanupService.cs | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ToDo.yaml b/ToDo.yaml index 5b512f4..75ac676 100755 --- a/ToDo.yaml +++ b/ToDo.yaml @@ -17,6 +17,9 @@ Server: JobCleanupService: Need to update notification email + CompanyEmailVerify: + Need to update notification email + Client: jobs/editor: Job Listing Skills exists but isn't implimented in the UI diff --git a/src/Server/Services/BackgroundServices/JobCleanupService.cs b/src/Server/Services/BackgroundServices/JobCleanupService.cs index fbd30fb..d943280 100644 --- a/src/Server/Services/BackgroundServices/JobCleanupService.cs +++ b/src/Server/Services/BackgroundServices/JobCleanupService.cs @@ -11,6 +11,17 @@ namespace BoredCareers.Services.TimerService { _em = em; } + public string Substitue(string message, string subString, string Replacement) { + for (int i = 0; i < (message.Length - subString.Length); i++) { + if (message.Substring(i, subString.Length) == subString) { + string before = message.Substring(0, i); + string after = message.Substring(i + subString.Length); + return before + Replacement + after; + } + } + return message; + } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { @@ -37,7 +48,10 @@ namespace BoredCareers.Services.TimerService { string[] emails = await _db.GetApplicationResponseEmailFromJobListing(listing.JobListingID); foreach (string email in emails) { // Send Notify Email - _em.Send(email, EmailService.JobAutoClosedSubject, EmailService.JobAutoClosedEmail); + string emailbody = EmailService.JobAutoClosedBody; + //Substitue(emailbody, "@job", listing.JobListingID); + + _em.Send(email, EmailService.JobAutoClosedSubject, emailbody); } }