From 5380eaffebc5cf0ba2f3cea26b7d65aa6c20c03d Mon Sep 17 00:00:00 2001 From: Andre Henriques Date: Sun, 13 Oct 2024 11:37:19 +0100 Subject: [PATCH] chore: update to so many things --- .../applications/ApplicationsController.kt | 125 +++++++---- api/src/main/resources/schema.sql | 3 +- site/README.md | 2 + site/src/lib/ApplicationsStore.svelte.ts | 2 + site/src/routes/cv/+page.svelte | 2 +- site/src/routes/graphs/+page.svelte | 206 +++++++++--------- .../routes/work-area/NewApplication.svelte | 51 +++++ .../routes/work-area/SearchApplication.svelte | 109 +++++++++ site/src/routes/work-area/WorkArea.svelte | 52 ++++- site/vite.config.ts | 3 + 10 files changed, 410 insertions(+), 145 deletions(-) create mode 100644 site/src/routes/work-area/NewApplication.svelte create mode 100644 site/src/routes/work-area/SearchApplication.svelte diff --git a/api/src/main/kotlin/com/andr3h3nriqu3s/applications/ApplicationsController.kt b/api/src/main/kotlin/com/andr3h3nriqu3s/applications/ApplicationsController.kt index cd77ee9..ba40a3f 100644 --- a/api/src/main/kotlin/com/andr3h3nriqu3s/applications/ApplicationsController.kt +++ b/api/src/main/kotlin/com/andr3h3nriqu3s/applications/ApplicationsController.kt @@ -1,11 +1,12 @@ package com.andr3h3nriqu3s.applications import java.sql.ResultSet -import java.util.UUID -import java.util.Date import java.text.SimpleDateFormat +import java.util.Date +import java.util.UUID import kotlin.collections.emptyList import kotlin.collections.setOf +import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.jdbc.core.JdbcTemplate import org.springframework.jdbc.core.RowMapper @@ -20,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestHeader import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import org.springframework.web.server.ResponseStatusException data class Application( var id: String, @@ -37,6 +39,7 @@ data class Application( var linked_application: String, var status_history: String, var application_time: String, + var create_time: String, var flairs: List, var views: List, ) { @@ -58,6 +61,7 @@ data class Application( rs.getString("linked_application"), rs.getString("status_history"), rs.getString("application_time"), + rs.getString("create_time"), emptyList(), emptyList(), ) @@ -109,6 +113,77 @@ class ApplicationsController( return CVData(application.company, application.recruiter, application.message, flairs) } + /** Create a new application from the link */ + @PostMapping(path = ["/link"], produces = [MediaType.APPLICATION_JSON_VALUE]) + public fun submitLink( + @RequestBody submit: SubmitRequest, + @RequestHeader("token") token: String + ): Application { + val user = sessionService.verifyTokenThrow(token) + + var application = + Application( + UUID.randomUUID().toString(), + submit.text, + submit.text, + submit.text, + "New Application", + user.id, + "", + "", + 0, + "", + "", + "", + "", + "", + "", + "", + emptyList(), + emptyList(), + ) + + if (!applicationService.createApplication(user, application)) { + throw ResponseStatusException(HttpStatus.CONFLICT, "Application already exists", null) + } + + print("Created: ") + println(application) + + return application + } + + @PostMapping(path = ["/text/flair"], produces = [MediaType.APPLICATION_JSON_VALUE]) + public fun textFlair( + @RequestBody info: FlairRequest, + @RequestHeader("token") token: String + ): Int { + val user = sessionService.verifyTokenThrow(token) + val application = applicationService.findApplicationById(user, info.id) + if (application == null) { + throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null) + } + + val flairs = flairService.listUser(user) + + var count = 0 + + for (flair: Flair in flairs) { + val regex = + Regex( + ".*" + flair.expr + ".*", + setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL) + ) + + if (regex.matches(info.text)) { + count += 1 + flairService.linkFlair(application, flair) + } + } + + return count + } + @PostMapping(path = ["/text"], produces = [MediaType.APPLICATION_JSON_VALUE]) public fun submitText( @RequestBody submit: SubmitRequest, @@ -179,7 +254,7 @@ class ApplicationsController( if (elm.contains("linkedin")) elm.split("?")[0] else elm, if (elm.contains("linkedin")) elm.split("?")[0] else null, if (elm.contains("linkedin")) elm.split("?")[0] else null, - "New Aplication", + "New Application", user.id, "", "", @@ -190,6 +265,7 @@ class ApplicationsController( "", "", "", + "", emptyList(), emptyList(), ) @@ -205,37 +281,6 @@ class ApplicationsController( return applications.size } - @PostMapping(path = ["/text/flair"], produces = [MediaType.APPLICATION_JSON_VALUE]) - public fun textFlair( - @RequestBody info: FlairRequest, - @RequestHeader("token") token: String - ): Int { - val user = sessionService.verifyTokenThrow(token) - val application = applicationService.findApplicationById(user, info.id) - if (application == null) { - throw NotFound() - } - - val flairs = flairService.listUser(user) - - var count = 0 - - for (flair: Flair in flairs) { - val regex = - Regex( - ".*" + flair.expr + ".*", - setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL) - ) - - if (regex.matches(info.text)) { - count += 1 - flairService.linkFlair(application, flair) - } - } - - return count - } - @PostMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE]) public fun list( @RequestBody info: ListRequest, @@ -267,10 +312,10 @@ class ApplicationsController( } application.status = info.status - val status_string = "${info.status}"; + val status_string = "${info.status}" var status_history = application.status_history.split(",").filter { it.length >= 1 } if (status_history.indexOf(status_string) == -1) { - status_history = status_history.plus("${info.status}"); + status_history = status_history.plus("${info.status}") } application.status_history = status_history.joinToString(",") { it } @@ -308,11 +353,11 @@ class ApplicationsController( var application = applicationService.findApplicationById(user, info.id) if (application == null) { - throw NotFound() + throw ResponseStatusException(HttpStatus.NOT_FOUND, "Application not found", null) } if (application.unique_url != null) { - throw BadRequest() + throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Application already has unique_url", null) } application.original_url = application.url @@ -341,6 +386,7 @@ class ApplicationsController( applicationService.update(application) application.flairs = flairService.listFromLinkApplicationId(application.id) + application.views = viewService.listFromApplicationId(application.id) return application } @@ -485,6 +531,7 @@ class ApplicationService( return false } + // Create time is auto created by the database db.update( "insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter, message, linked_application, status_history, application_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", application.id, @@ -527,7 +574,6 @@ class ApplicationService( .toList() } - return db.query( "select * from applications where user_id=? and status=? order by title asc;", arrayOf(user.id, info.status), @@ -537,6 +583,7 @@ class ApplicationService( } public fun update(application: Application): Application { + // I don't want ot update create_time db.update( "update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, status=?, company=?, recruiter=?, message=?, linked_application=?, status_history=?, application_time=? where id=?", application.url, diff --git a/api/src/main/resources/schema.sql b/api/src/main/resources/schema.sql index fcc52dd..72881ee 100644 --- a/api/src/main/resources/schema.sql +++ b/api/src/main/resources/schema.sql @@ -25,7 +25,8 @@ create table if not exists applications ( extra_data text, status integer, linked_application text default '', - application_time text default '' + application_time text default '', + create_time timestamp default now() ); create table if not exists views ( diff --git a/site/README.md b/site/README.md index 66158d8..119a2ab 100644 --- a/site/README.md +++ b/site/README.md @@ -40,3 +40,5 @@ ## Building ## TODO https://www.glassdoor.co.uk/job-listing/junior-software-developer-full-stack-onnec-group-JV_IC2671300_KO0,36_KE37,48.htm?jl=1009478590946&utm_source=jobsForYou&utm_medium=email&utm_content=jobs-for-you-jobsForYou-jobpos5-1009478590946&utm_campaign=jobsForYou&src=GD_JOB_AD&uido=5EF1E454F911F36A51BB1DD9CB97C8DB&ao=1136043&jrtk=6-y100011i9mkhgisgqqb801ab7157ddf833b2e1c&cs=1_57b69a24&s=362&t=REC_JOBS&pos=105&guid=00000192656d7c18b164f224f0a88e83&jobListingId=1009478590946&ea=1&vt=e&cb=1728410338190&ctt=1728466768707 + +https://careers.veeva.com/job/866d4776-9d23-4311-ab16-4ebff725984d/frontend-engineer-react-remote-london-united-kingdom/ diff --git a/site/src/lib/ApplicationsStore.svelte.ts b/site/src/lib/ApplicationsStore.svelte.ts index bcf71bd..f27eefe 100644 --- a/site/src/lib/ApplicationsStore.svelte.ts +++ b/site/src/lib/ApplicationsStore.svelte.ts @@ -49,6 +49,8 @@ export type Application = { message: string; linked_application: string; application_time: string; + create_time: string; + status_history: string; flairs: Flair[]; views: View[]; }; diff --git a/site/src/routes/cv/+page.svelte b/site/src/routes/cv/+page.svelte index 26a6295..044f838 100644 --- a/site/src/routes/cv/+page.svelte +++ b/site/src/routes/cv/+page.svelte @@ -150,7 +150,7 @@

Your Ad & My skills {#if flairs.length > 0} diff --git a/site/src/routes/graphs/+page.svelte b/site/src/routes/graphs/+page.svelte index 95e35bf..8500602 100644 --- a/site/src/routes/graphs/+page.svelte +++ b/site/src/routes/graphs/+page.svelte @@ -21,119 +21,125 @@ | AsEnum | 'Linkedin' | 'Glassdoor' - | 'Unknown Source' - | 'Applications'; + | 'Direct Source'; - let nodeTypes: Record = { - [ApplicationStatus.ToApply]: 0, - [ApplicationStatus.WorkingOnIt]: 0, - [ApplicationStatus.Ignore]: 0, - [ApplicationStatus.ApplyedButSaidNo]: 0, - [ApplicationStatus.Expired]: 0, - [ApplicationStatus.Applyed]: 0, - [ApplicationStatus.TasksToDo]: 0, - [ApplicationStatus.LinkedApplication]: 0, - Linkedin: 0, - Glassdoor: 0, - 'Unknown Source': 0, - Applications: applications.length - }; + let graph = {} as Record>; - const showPercentage: string[] = [ - `${ApplicationStatus.ToApply}`, - `${ApplicationStatus.Ignore}`, - `${ApplicationStatus.Expired}`, - `${ApplicationStatus.Applyed}`, - `${ApplicationStatus.LinkedApplication}`, - `${ApplicationStatus.ApplyedButSaidNo}`, - `${ApplicationStatus.TasksToDo}` - ]; - - const baseLinks: { source: NodeType; target: NodeType; value: 0 | 1; end?: boolean }[] = - [ - { source: 'Linkedin', target: 'Applications', value: 0 }, - { source: 'Glassdoor', target: 'Applications', value: 0 }, - { source: 'Unknown Source', target: 'Applications', value: 0 }, - { source: 'Applications', target: ApplicationStatus.ToApply, value: 1 }, - { source: 'Applications', target: ApplicationStatus.Ignore, value: 1 }, - { source: 'Applications', target: ApplicationStatus.Expired, value: 1 }, - { source: 'Applications', target: ApplicationStatus.Applyed, value: 1 }, - { - source: 'Applications', - target: ApplicationStatus.LinkedApplication, - value: 1 - }, - - { - source: ApplicationStatus.Applyed, - target: ApplicationStatus.ApplyedButSaidNo, - value: 1 - }, - { - source: ApplicationStatus.Applyed, - target: ApplicationStatus.TasksToDo, - value: 1 - } - ]; + function addGraph(inSource: NodeType, inTarget: NodeType) { + const source = `${inSource}`; + const target = `${inTarget}`; + if (graph[source] == undefined) { + graph[source] = {} as Record; + } + graph[source][target] = (graph[source][target] ?? 0) + 1; + return target as NodeType; + } applications.forEach((a) => { + let source: NodeType; + if (a.url.includes('linkedin')) { - nodeTypes['Linkedin'] += 1; + source = 'Linkedin'; } else if (a.url.includes('glassdoor')) { - nodeTypes['Glassdoor'] += 1; + source = 'Glassdoor'; } else { - nodeTypes['Unknown Source'] += 1; - } - if (a.status !== ApplicationStatus.WorkingOnIt) { - nodeTypes[a.status] += 1; - } else { - nodeTypes[ApplicationStatus.ToApply] += 1; + source = 'Direct Source'; } + if ( - [ApplicationStatus.ApplyedButSaidNo, ApplicationStatus.TasksToDo].includes( + ( + [ + ApplicationStatus.Ignore, + ApplicationStatus.Expired, + ApplicationStatus.LinkedApplication, + ApplicationStatus.ToApply, + ApplicationStatus.Applyed + ] as AsEnum[] + ).includes(a.status) + ) { + addGraph(source, a.status); + return; + } + + // Edge case for working on it + if (a.status === ApplicationStatus.WorkingOnIt) { + addGraph(source, ApplicationStatus.ToApply); + return; + } + + if (a.status == ApplicationStatus.ApplyedButSaidNo) { + const history = a.status_history.split(','); + source = addGraph(source, ApplicationStatus.Applyed); + if (history.includes(`${ApplicationStatus.TasksToDo}`)) { + source = addGraph(source, ApplicationStatus.TasksToDo); + } + addGraph(source, ApplicationStatus.ApplyedButSaidNo); + } else if ( + ([ApplicationStatus.TasksToDo] as AsEnum[]).includes( a.status ) ) { - nodeTypes[ApplicationStatus.Applyed] += 1; + addGraph(source, ApplicationStatus.Applyed); + addGraph(ApplicationStatus.Applyed, a.status); } }); - let inNodes: string[] = []; - - let nodes = (Object.keys(nodeTypes) as (keyof typeof nodeTypes)[]) - .filter((a) => nodeTypes[a] > 0) - .map((a, i) => { - inNodes.push(`${a}`); - const base = { - value: nodeTypes[a], - originalValue: a, - id: '', - index: i, - percentage: Math.trunc((nodeTypes[a] / applications.length) * 100), - end: showPercentage.includes(`${a}`) - }; - if (Number.isNaN(Number(a))) { - base.id = a as string; - } else { - base.id = ApplicationStatusMaping[a as AsEnum]; + let inNodes: string[] = Object.keys(graph).reduce((acc, elm) => { + const arr = Object.keys(graph[elm]).concat(elm); + for (const i of arr) { + if (!acc.includes(i)) { + acc.push(i); } - return base; - }); + } + return acc; + }, [] as string[]); - const links = baseLinks - .filter( - (link) => - inNodes.includes(`${link.source}`) && inNodes.includes(`${link.target}`) - ) - .map((link) => { - const source = inNodes.indexOf(`${link.source}`); - const target = inNodes.indexOf(`${link.target}`); - return { - source: source, - target: target, - value: [nodes[source], nodes[target]][link.value].value - }; - }); + function getGraphValueFor(node: string): number { + return Object.keys(graph).reduce((acc, i) => { + if (i == node) return acc; + if (graph[i][node] != undefined) { + return acc + graph[i][node]; + } + return acc; + }, 0); + } + + let nodes = inNodes.map((node, i) => { + let name = ''; + if (Number.isNaN(Number(node))) { + name = node; + } else { + name = ApplicationStatusMaping[Number(node) as AsEnum]; + } + const value = getGraphValueFor(node); + const base = { + value: value, + originalValue: node, + id: name, + index: i, + percentage: Math.trunc((value / applications.length) * 100), + end: true + }; + return base; + }); + + type Link = { + source: number; + target: number; + value: number; + } + + const links = Object.keys(graph).reduce((acc, source) => { + return acc.concat(Object.keys(graph[source]).map((target) => { + const ns = inNodes.indexOf(`${source}`); + const nt = inNodes.indexOf(`${target}`); + return { + source: ns, + target: nt, + value: graph[source][target] + }; + })); + }, [] as Link[]) const bounding = chartDiv.getBoundingClientRect(); @@ -146,6 +152,8 @@ sankey.nodes(nodes).links(links).layout(32); + console.log("here2", nodes, links); + const svg = d3 .select(chartDiv) .append('svg') @@ -171,6 +179,9 @@ .append('path') .attr('class', 'link') .attr('d', path) + .style('stroke', function(d) { + return d3.rgb(color[d.source.index]).toString(); + }) .style('stroke-width', function (d) { return Math.max(1, d.dy); }) @@ -269,7 +280,6 @@ :global(.link) { fill: none; - stroke: #000; - stroke-opacity: 0.2; + stroke-opacity: 0.5; } diff --git a/site/src/routes/work-area/NewApplication.svelte b/site/src/routes/work-area/NewApplication.svelte new file mode 100644 index 0000000..92baa04 --- /dev/null +++ b/site/src/routes/work-area/NewApplication.svelte @@ -0,0 +1,51 @@ + + + +
+
+ + +
+
+
diff --git a/site/src/routes/work-area/SearchApplication.svelte b/site/src/routes/work-area/SearchApplication.svelte new file mode 100644 index 0000000..c16c69e --- /dev/null +++ b/site/src/routes/work-area/SearchApplication.svelte @@ -0,0 +1,109 @@ + + + +
+ +
+ {applications.length} +
+
+
+ {#each applications.filter((i) => { + if (application && i.id == application.id) { + return false; + } + if (!filter) { + return true; + } + + if (filter.includes('@') && i.company) { + const f = new RegExp(filter.split('@')[0].trim(), 'ig'); + const c = new RegExp(filter.split('@')[1].trim(), 'ig'); + return i.title.match(f) && i.company.match(c); + } + + const f = new RegExp(filter, 'ig'); + + let x = i.title; + + if (i.company) { + x = `${x} @ ${i.company}`; + } + + return x.match(f); + }) as item} +
+ +
+ {/each} +
+
diff --git a/site/src/routes/work-area/WorkArea.svelte b/site/src/routes/work-area/WorkArea.svelte index b939bb0..04e9a11 100644 --- a/site/src/routes/work-area/WorkArea.svelte +++ b/site/src/routes/work-area/WorkArea.svelte @@ -3,7 +3,8 @@ ApplicationStatus, ApplicationStatusMaping, applicationStore, - type Application + type Application, + type AsEnum } from '$lib/ApplicationsStore.svelte'; import { put, preventDefault, post, get, deleteR } from '$lib/utils'; @@ -14,6 +15,8 @@ import DropZone from './DropZone.svelte'; import { userStore } from '$lib/UserStore.svelte'; import LinkApplication from './LinkApplication.svelte'; + import SearchApplication from './SearchApplication.svelte'; + import NewApplication from './NewApplication.svelte'; let activeItem: Application | undefined = $state(); @@ -24,6 +27,8 @@ let lastExtData: any = $state(undefined); let autoExtData = false; + let showExtraData = $state(false); + async function activate(item?: Application) { if (!item) { return; @@ -113,8 +118,6 @@ } } - console.log('setting up interest'); - window.addEventListener('message', onMessage); window.postMessage({ type: 'REGISTER_INTEREST' }); return () => { @@ -156,7 +159,7 @@ applicationStore.loadItem = undefined; }); - async function moveStatus(status: number) { + async function moveStatus(status: AsEnum, moveOut = true) { if (!activeItem) return; // Deactivate active item try { @@ -171,7 +174,11 @@ } applicationStore.loadApplications(true); applicationStore.loadAplyed(true); - activeItem = undefined; + if (moveOut) { + activeItem = undefined; + } else { + activeItem.status = status; + } //openedWindow?.close(); //openedWindow = undefined; } @@ -240,6 +247,20 @@ {statusMapping}

{/if} + {#if showExtraData} +
+
Id
+
+ {activeItem.id} +
+
+
+
Create Time
+
+ {activeItem.create_time} +
+
+ {/if}
@@ -395,6 +416,13 @@ > 👋 +
{#if applicationStore.dragging} @@ -423,13 +451,15 @@ > Ignore it + {/if} + {#if [ApplicationStatus.WorkingOnIt, ApplicationStatus.Expired].includes(activeItem.status)} { if (activeItem && activeItem.status === ApplicationStatus.Expired) { - moveStatus(ApplicationStatus.ToApply); + moveStatus(ApplicationStatus.ToApply, false); } else { moveStatus(ApplicationStatus.Expired); } @@ -437,7 +467,9 @@ > Mark as expired + {/if} + {#if activeItem.status === ApplicationStatus.WorkingOnIt} remove()} >Delete it (activeItem = item)} /> {/if} + + (activeItem = item)} /> + + { + activate(item); + }} +/> diff --git a/site/vite.config.ts b/site/vite.config.ts index c913085..7283387 100644 --- a/site/vite.config.ts +++ b/site/vite.config.ts @@ -3,6 +3,9 @@ import { defineConfig } from 'vite'; export default defineConfig({ plugins: [sveltekit()], + server: { + host: true, + }, build: { commonjsOptions: { esmExternals: true