diff --git a/ToDo.yaml b/ToDo.yaml
index 883180f..80a2eb7 100755
--- a/ToDo.yaml
+++ b/ToDo.yaml
@@ -15,6 +15,19 @@ Server:
Emails:
Make emails follow theme of website better
+ JobListingController:
+ Dont refresh on every filter edit
+ Line 63 is terrible and need to be fixed in the JobListing DB JobListingController
+ bools and numbers are getting strigified which breaks the mysql parameters
+
+ Validation:
+ Alot of the validation is only taking place client side.
+ Need to validate all inputs before processing
+ Phone number
+ Email Address
+ City, CountryCode, PostalCode
+ When applying to a job, The server doesnt make sure that the company only fields are not modified
+
Client:
jobs/editor:
Want to add completed job listing preview at end of carosel
diff --git a/database/mistox.sql b/database/mistox.sql
index 2d08131..068b5b0 100755
--- a/database/mistox.sql
+++ b/database/mistox.sql
@@ -193,8 +193,8 @@ CREATE TABLE IF NOT EXISTS `PostalCodes` (
`StateCode` varchar(20),
`County` varchar(100),
`CountyCode` varchar(20),
- `Admin` varchar(100),
- `AdminCode` varchar(20),
+ `Community` varchar(100),
+ `CommunityCode` varchar(20),
`Latitude` float,
`Longitude` float,
`Accuracy` varchar(2)
diff --git a/src/Client/src/app/models/JobFilter.ts b/src/Client/src/app/models/JobFilter.ts
new file mode 100644
index 0000000..7ccd085
--- /dev/null
+++ b/src/Client/src/app/models/JobFilter.ts
@@ -0,0 +1,11 @@
+export class JobFilter {
+ public JobsPerPage: number = 20;
+ public CurrentPage: number = 1;
+ public CountryCode: string | null = null;
+ public PostalCode: string | null = null;
+ public Distance: number | null = null;
+ public JobType: string | null = null;
+ public Remote: boolean | null = null;
+ public SalaryMin: number | null = null;
+ public SalaryMax: number | null = null;
+}
\ No newline at end of file
diff --git a/src/Client/src/app/pages/jobs/jobs.component.html b/src/Client/src/app/pages/jobs/jobs.component.html
index cdfd17c..60fd90c 100644
--- a/src/Client/src/app/pages/jobs/jobs.component.html
+++ b/src/Client/src/app/pages/jobs/jobs.component.html
@@ -1,3 +1,35 @@
+
+
+
+
Country
+
+
+
+
Postal Code
+
+
+
+
Distance
+
+
+
+
Job Type
+
+
+
+
Remote
+
+
+
+
Minimum Salary
+
+
+
+
Maximum Salary
+
+
+
+
@for (cur of JobListingPage; track cur.id){
@@ -21,4 +53,8 @@
}
+
+
Jobs Per Page
+
+
\ No newline at end of file
diff --git a/src/Client/src/app/pages/jobs/jobs.component.ts b/src/Client/src/app/pages/jobs/jobs.component.ts
index 827bd86..04b71d3 100644
--- a/src/Client/src/app/pages/jobs/jobs.component.ts
+++ b/src/Client/src/app/pages/jobs/jobs.component.ts
@@ -6,6 +6,7 @@ import { Title } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { JobListing } from 'app/models/JobListing';
import { Authentication } from 'app/services/Authentication';
+import { JobFilter } from 'app/models/JobFilter';
@Component({
selector: 'main-jobs',
@@ -15,7 +16,7 @@ import { Authentication } from 'app/services/Authentication';
})
export class JobsComponent {
- public MyJobListings: JobListing[] = [];
+ public currentFilter: JobFilter;
public JobListingPage: JobListing[] = [];
public ErrorMsg: string = "";
@@ -23,10 +24,46 @@ export class JobsComponent {
constructor( private http: HttpClient, private router: Router, private route: ActivatedRoute, private title: Title, public auth: Authentication ) {
this.title.setTitle("Jobs | BoredCareers");
+ this.currentFilter = new JobFilter();
};
ngOnInit(){
- this.http.get("api/joblisting?PageQuantity=" + 10 + "&Page=" + 1).subscribe({
+ this.http.get("api/joblisting?PageQuantity=" + this.currentFilter.JobsPerPage + "&Page=" + this.currentFilter.CurrentPage).subscribe({
+ next: data => {
+ this.JobListingPage = data;
+ },
+ error: err => {
+ this.ErrorMsg = err.error;
+ }
+ });
+ }
+
+ reloadFilters(){
+ var queryBuilder = "api/joblisting?PageQuantity=" + this.currentFilter.JobsPerPage + "&Page=" + this.currentFilter.CurrentPage;
+
+ if (this.currentFilter.PostalCode != null){
+ queryBuilder += "&PC=" + this.currentFilter.PostalCode;
+ }
+ if (this.currentFilter.CountryCode != null){
+ queryBuilder += "&CC=" + this.currentFilter.CountryCode;
+ }
+ if (this.currentFilter.Distance != null){
+ queryBuilder += "&D=" + this.currentFilter.Distance;
+ }
+ if (this.currentFilter.JobType != null){
+ queryBuilder += "&JT=" + this.currentFilter.JobType;
+ }
+ if (this.currentFilter.Remote != null){
+ queryBuilder += "&R=" + this.currentFilter.Remote;
+ }
+ if (this.currentFilter.SalaryMin != null){
+ queryBuilder += "&SMI=" + this.currentFilter.SalaryMin;
+ }
+ if (this.currentFilter.SalaryMax != null){
+ queryBuilder += "&SMA=" + this.currentFilter.SalaryMax;
+ }
+
+ this.http.get(queryBuilder).subscribe({
next: data => {
this.JobListingPage = data;
},
diff --git a/src/Client/src/app/services/Validation.ts b/src/Client/src/app/services/Validation.ts
index 0887ad9..ddec9c9 100644
--- a/src/Client/src/app/services/Validation.ts
+++ b/src/Client/src/app/services/Validation.ts
@@ -74,16 +74,31 @@ export class Validation {
///////// HELPER FUNCTIONS /////////
isPrivateIPv6(ip: string): boolean {
- try {
- const normalized = ip.replace(/^\[|\]$/g, '').toLowerCase();
- if (normalized === '::1') return true;
- if (normalized.startsWith('fc') || normalized.startsWith('fd')) return true;
- const first4 = normalized.slice(0, 4);
- if (first4 >= 'fe80' && first4 <= 'febf') return true;
- return false;
- } catch {
- return true;
+ try {
+ const normalized = ip.replace(/^\[|\]$/g, '').toLowerCase();
+ if (normalized === '::1') return true;
+ if (normalized.startsWith('fc') || normalized.startsWith('fd')) return true;
+ const first4 = normalized.slice(0, 4);
+ if (first4 >= 'fe80' && first4 <= 'febf') return true;
+ return false;
+ } catch {
+ return true;
+ }
+ }
+
+ ///////// GETTERS /////////
+
+ get ValidCountries(): string[] {
+ return ['AD', 'AE', 'AI', 'AL', 'AR', 'AS', 'AT', 'AU', 'AX', 'AZ', 'BD', 'BE', 'BG',
+ 'BM', 'BR', 'BY', 'CA', 'CC', 'CH', 'CL', 'CN', 'CO', 'CR', 'CX', 'CY', 'CZ',
+ 'DE', 'DK', 'DO', 'DZ', 'EC', 'EE', 'ES', 'FI', 'FK', 'FM', 'FO', 'FR', 'GB',
+ 'GF', 'GG', 'GI', 'GL', 'GP', 'GS', 'GT', 'GU', 'HK', 'HM', 'HN', 'HR', 'HT',
+ 'HU', 'ID', 'IE', 'IM', 'IN', 'IO', 'IS', 'IT', 'JE', 'JP', 'KE', 'KR', 'LI',
+ 'LK', 'LT', 'LU', 'LV', 'MA', 'MC', 'MD', 'MH', 'MK', 'MO', 'MP', 'MQ', 'MT',
+ 'MW', 'MX', 'MY', 'NC', 'NF', 'NL', 'NO', 'NR', 'NU', 'NZ', 'PA', 'PE', 'PF',
+ 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PT', 'PW', 'RE', 'RO', 'RS', 'RU', 'SE',
+ 'SG', 'SI', 'SJ', 'SK', 'SM', 'TC', 'TH', 'TR', 'UA', 'US', 'UY', 'VA', 'VI',
+ 'WF', 'WS', 'YT', 'ZA'];
}
-}
}
\ No newline at end of file
diff --git a/src/Server/Controllers/JobListingController.cs b/src/Server/Controllers/JobListingController.cs
index 18e90c3..647050c 100644
--- a/src/Server/Controllers/JobListingController.cs
+++ b/src/Server/Controllers/JobListingController.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using BoredCareers.Services.DatabaseService;
using BoredCareers.Entities;
+using Org.BouncyCastle.Tls;
namespace BoredCareers.Controllers {
[ApiController]
@@ -31,8 +32,25 @@ namespace BoredCareers.Controllers {
}
[HttpGet]
- public async Task GetJobListings(int Page = 1, int PageQuantity = 25) {
- JobListing[] jobListings = await _databaseService.GetJobListingPage(Page, PageQuantity);
+ public async Task GetJobListings(string? CC, string? PC, float? D, string? JT, bool? R, int? SMI, int? SMA, int Page = 1, int PageQuantity = 25 ) {
+
+ // Get all relevant postal codes
+ List PostalCodes = new List();
+ if (PC != null && CC != null && D != null) {
+ Location[] nearby = await _databaseService.GetNearbyLocations(PC, CC, D.Value);
+ foreach (Location cur in nearby) {
+ PostalCodes.Add(cur.PostalCode);
+ }
+ } else if (PC != null) {
+ PostalCodes.Add(PC);
+ }
+
+ string[]? pc = null;
+ if (PostalCodes.Count > 0) {
+ pc = PostalCodes.ToArray();
+ }
+
+ JobListing[] jobListings = await _databaseService.GetJobListingPage(Page, PageQuantity, pc, CC, JT, R, SMI, SMA );
return Ok(jobListings);
}
diff --git a/src/Server/Controllers/LocationController.cs b/src/Server/Controllers/LocationController.cs
index e855975..90ab731 100644
--- a/src/Server/Controllers/LocationController.cs
+++ b/src/Server/Controllers/LocationController.cs
@@ -1,25 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using BoredCareers.Services.DatabaseService;
using BoredCareers.Entities;
-using BoredCareers.Services;
namespace BoredCareers.Controllers {
[ApiController]
[Route("api/location")]
public class LocationController : MistoxControllerBase {
- public LocationController(DatabaseService db, EmailService emailContext) : base(db) {}
+ public LocationController(DatabaseService db) : base(db) {}
[HttpGet]
public async Task GetCompany(string PostalCode, string CountryCode, float MaxDistanceKm) {
if (isLoggedIn()) {
-
- Location? MyZIP = await _databaseService.GetLocation(PostalCode, CountryCode);
- if (MyZIP != null) {
- Location[] places = await _databaseService.GetNearbyLocations(MyZIP.Latitude, MyZIP.Longitude, MyZIP.CountryCode, MaxDistanceKm);
- return Ok(places.ToArray());
- }
- return NotFound("Postal + CountryCode not found");
+ Location[] places = await _databaseService.GetNearbyLocations(PostalCode, CountryCode, MaxDistanceKm);
+ return Ok(places.ToArray());
}
return NotFound("Not logged in");
}
diff --git a/src/Server/Services/DatabaseService/JobListing.cs b/src/Server/Services/DatabaseService/JobListing.cs
index 8dad416..28029ca 100644
--- a/src/Server/Services/DatabaseService/JobListing.cs
+++ b/src/Server/Services/DatabaseService/JobListing.cs
@@ -7,21 +7,73 @@ using System.Data.Common;
namespace BoredCareers.Services.DatabaseService {
public partial class DatabaseService {
- public async Task GetJobListingPage(int PageNumber, int CountPerPage) {
+ public async Task GetJobListingPage(int PageNumber, int CountPerPage, string[]? PostalCodes = null, string? CountryCode = null, string? JobType = null, bool? Remote = null, int? SalaryMin = null, int? SalaryMax = null ) {
List joblistings = new List();
using (MySqlConnection connection = GetConnection()) {
await connection.OpenAsync();
- string command = @"
- SELECT *
- FROM JobListing
- WHERE IsDeleted = FALSE
- ORDER BY CreatedTime DESC
- LIMIT @PageSize OFFSET @PageNumber;
- ";
- MySqlCommand cmd = new MySqlCommand(command, connection);
+ string select = "SELECT * FROM JobListing";
+ string order = " ORDER BY CreatedTime DESC";
+ string limit = " LIMIT @PageSize OFFSET @PageNumber;";
+
+ List Filters = new List();
+ List Parameters = new List();
+ List ParameterName = new List();
+
+ if (PostalCodes != null) {
+ for (int i = 0; i < PostalCodes.Length; i++) {
+ Filters.Add("PostalCode");
+ Parameters.Add(PostalCodes[i]);
+ ParameterName.Add("@PostalCode" + i);
+ }
+ }
+
+ if (CountryCode != null) {
+ Filters.Add("Country");
+ Parameters.Add(CountryCode);
+ ParameterName.Add("@CountryCode");
+ }
+
+ if (JobType != null) {
+ Filters.Add("JobType");
+ Parameters.Add(JobType);
+ ParameterName.Add("@JobType");
+ }
+
+ if (Remote != null) {
+ Filters.Add("Remote");
+ Parameters.Add(Remote.Value.ToString());
+ ParameterName.Add("@Remote");
+ }
+
+ if (SalaryMin != null) {
+ Filters.Add("SalaryMin");
+ Parameters.Add(SalaryMin.Value.ToString());
+ ParameterName.Add("@SalaryMin");
+ }
+
+ if (SalaryMax != null) {
+ Filters.Add("SalaryMax");
+ Parameters.Add(SalaryMax.Value.ToString());
+ ParameterName.Add("@SalaryMax");
+ }
+
+ string filter = " WHERE IsDeleted = False";
+ for (int i = 0; i < Filters.Count; i++) {
+ if (Filters[i] == "PostalCode" && i != 0) {
+ filter += " OR ";
+ } else {
+ filter += " AND ";
+ }
+ filter += Filters[i] + " = " + ParameterName[i];
+ }
+
+ MySqlCommand cmd = new MySqlCommand(select + filter + order + limit, connection);
cmd.Parameters.AddWithValue("@PageSize", CountPerPage);
cmd.Parameters.AddWithValue("@PageNumber", (PageNumber - 1) * CountPerPage);
+ for (int i = 0; i < Filters.Count; i++) {
+ cmd.Parameters.AddWithValue( ParameterName[i], Parameters[i] );
+ }
using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) {
diff --git a/src/Server/Services/DatabaseService/Location.cs b/src/Server/Services/DatabaseService/Location.cs
index 467d00b..16efe83 100644
--- a/src/Server/Services/DatabaseService/Location.cs
+++ b/src/Server/Services/DatabaseService/Location.cs
@@ -7,64 +7,35 @@ using System.Text;
namespace BoredCareers.Services.DatabaseService {
public partial class DatabaseService {
- public async Task GetLocation(string PostalCode, string CountryCode) {
- using (MySqlConnection connection = GetConnection()) {
- await connection.OpenAsync();
- string command = @"
- SELECT PostalCode, CountryCode, Latitude, Longitude, City
- FROM PostalCodes
- WHERE PostalCode = @PostalCode
- AND CountryCode = @CountryCode;
- ";
-
- MySqlCommand cmd = new MySqlCommand(command, connection);
- cmd.Parameters.AddWithValue("@PostalCode", PostalCode);
- cmd.Parameters.AddWithValue("@CountryCode", CountryCode);
-
- using (DbDataReader reader = await cmd.ExecuteReaderAsync()) {
- while (await reader.ReadAsync()) {
- string _city = reader.GetString("City");
- string _postalCode = reader.GetString("PostalCode");
- string _countryCode = reader.GetString("CountryCode");
- float _latitude = reader.GetFloat("Latitude");
- float _longitude = reader.GetFloat("Longitude");
-
- return new Location() {
- City = _city,
- PostalCode = _postalCode,
- CountryCode = _countryCode,
- Latitude = _latitude,
- Longitude = _longitude
- };
- }
- }
- }
- return null;
- }
-
- public async Task GetNearbyLocations(float Latitude, float Longitude, string CountryCode, float MaxDistanceKm) {
+ public async Task GetNearbyLocations(string PostalCode, string CountryCode, float MaxDistanceKm) {
List closePostalCodes = new List();
using (MySqlConnection connection = GetConnection()) {
await connection.OpenAsync();
string command = @"
- SELECT PostalCode, CountryCode, Latitude, Longitude, City, (
- 6371 * acos(
- cos(radians(@Latitude)) *
- cos(radians(Latitude)) *
- cos(radians(Longitude) - radians(@Longitude)) +
- sin(radians(@Latitude)) *
- sin(radians(Latitude))
- )
- ) AS distance_km
- FROM PostalCodes
- WHERE countrycode = @CountryCode
+ SELECT
+ pc2.PostalCode,
+ pc2.CountryCode,
+ pc2.Latitude,
+ pc2.Longitude,
+ pc2.City,
+ (
+ 6371 * acos(
+ cos(radians(pc1.Latitude)) *
+ cos(radians(pc2.Latitude)) *
+ cos(radians(pc2.Longitude) - radians(pc1.Longitude)) +
+ sin(radians(pc1.Latitude)) *
+ sin(radians(pc2.Latitude))
+ )
+ ) AS distance_km
+ FROM PostalCodes pc1
+ JOIN PostalCodes pc2 ON pc2.CountryCode = 'us'
+ WHERE pc1.PostalCode = @PostalCode AND pc1.CountryCode = @CountryCode
HAVING distance_km <= @MaxDistanceKm
ORDER BY distance_km;
";
MySqlCommand cmd = new MySqlCommand(command, connection);
- cmd.Parameters.AddWithValue("@Latitude", Latitude);
- cmd.Parameters.AddWithValue("@Longitude", Longitude);
+ cmd.Parameters.AddWithValue("@PostalCode", PostalCode);
cmd.Parameters.AddWithValue("@CountryCode", CountryCode);
cmd.Parameters.AddWithValue("@MaxDistanceKm", MaxDistanceKm);