chore: more work on cv and on main page

This commit is contained in:
Andre Henriques 2024-09-30 15:15:08 +01:00
parent 8bb068499b
commit 407b955950
13 changed files with 285 additions and 166 deletions

View File

@ -1,5 +1,5 @@
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/applications
spring.datasource.url=jdbc:postgresql://kronos:5432/applications
spring.datasource.username=applications
spring.datasource.password=applications

View File

@ -30,7 +30,9 @@ data class Application(
var extra_data: String,
var payrange: String,
var status: Int,
var flairs: List<Flair>
var company: String,
var recruiter: String,
var flairs: List<Flair>,
) {
companion object : RowMapper<Application> {
override public fun mapRow(rs: ResultSet, rowNum: Int): Application {
@ -44,6 +46,8 @@ data class Application(
rs.getString("extra_data"),
rs.getString("payrange"),
rs.getInt("status"),
rs.getString("company"),
rs.getString("recruiter"),
emptyList()
)
}
@ -60,6 +64,8 @@ data class FlairRequest(val id: String, val text: String)
data class UpdateUrl(val id: String, val url: String)
data class CVData(val company: String, val recruiter: String, val flairs: List<SimpleFlair>)
@RestController
@ControllerAdvice
@RequestMapping("/api/application")
@ -69,6 +75,23 @@ class ApplicationsController(
val flairService: FlairService
) {
@GetMapping(path = ["/cv/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun getCV(
@PathVariable id: String,
@RequestHeader("token") token: String
): CVData? {
print("here!");
val user = sessionService.verifyToken(token);
val application = applicationService.findApplicationByIdNoUser(id);
if (application == null) return null;
val flairs = application.flairs.map {it.toFlairSimple()};
return CVData(application.company, application.recruiter, flairs);
}
@PostMapping(path = ["/text"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun submitText(
@RequestBody submit: SubmitRequest,
@ -179,6 +202,8 @@ class ApplicationsController(
"",
"",
0,
"",
"",
emptyList()
)
}
@ -435,13 +460,27 @@ class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
return application
}
public fun findApplicationByIdNoUser(id: String): Application? {
var applications = db.query("select * from applications where id=?", arrayOf(id), Application).toList()
if (applications.size == 0) {
return null
}
var application = applications[0]
application.flairs = flairService.listFromLinkApplicationId(application.id)
return application
}
public fun createApplication(user: UserDb, application: Application): Boolean {
if (this.findApplicationByUrl(user, application.url, application.unique_url) != null) {
return false
}
db.update(
"insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status) values (?, ?, ?, ?, ?, ?, ?, ?, ?);",
"insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
application.id,
application.url,
application.original_url,
@ -451,6 +490,8 @@ class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
application.extra_data,
application.payrange,
application.status,
application.company,
application.recruiter
)
return true
@ -476,7 +517,7 @@ class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
public fun update(application: Application): Application {
db.update(
"update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, status=? where id=?",
"update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, status=?, company=?, recruiter=? where id=?",
application.url,
application.original_url,
application.unique_url,
@ -485,6 +526,8 @@ class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
application.extra_data,
application.payrange,
application.status,
application.company,
application.recruiter,
application.id,
)
return application

View File

@ -68,6 +68,12 @@ class FlairController(val sessionService: SessionService, val flairService: Flai
}
}
data class SimpleFlair(
val name: String,
val description: String,
val color: String,
)
data class Flair(
var id: String,
var user_id: String,
@ -88,6 +94,10 @@ data class Flair(
)
}
}
fun toFlairSimple(): SimpleFlair {
return SimpleFlair(this.name, this.description, this.color);
}
}
data class FlairLink(var id: String, var application_id: String, var flair_id: String)
@ -134,14 +144,14 @@ public class FlairService(val db: JdbcTemplate) {
public fun listFromLinkApplicationId(id: String): List<Flair> =
db.query(
"select f.id, f.user_id, f.color, f.name, f.expr, f.description from flair_link as fl inner join flair as f on f.id = fl.flair_id where application_id=?;",
"select f.id, f.user_id, f.color, f.name, f.expr, f.description from flair_link as fl inner join flair as f on f.id = fl.flair_id where application_id=? order by name asc;",
arrayOf(id),
Flair
)
.toList()
public fun listUser(user: UserDb): List<Flair> =
db.query("select * from flair where user_id=?;", arrayOf(user.id), Flair).toList()
db.query("select * from flair where user_id=? order by name asc;", arrayOf(user.id), Flair).toList()
public fun getById(user: UserDb, id: String): Flair? {
val items =

View File

@ -16,6 +16,8 @@ create table if not exists applications (
url text not null,
original_url text,
unique_url text,
company text,
recruiter text,
title text,
user_id text,
extra_data text,

View File

@ -1,20 +1,28 @@
browser.runtime.onMessage.addListener(function (message) {
browser.runtime.onMessage.addListener((message) => {
if (message.type === "MY_GET_URL_R") {
window.postMessage(message);
} else if (message.type === "GET_DATA_FROM_PAGE") {
let company = document
const company = document
.querySelector('header[data-test="job-details-header"]')
.children[0].children[0].querySelector("h4").innerHTML;
let jobTitle = document
const jobTitle = document
.querySelector('header[data-test="job-details-header"]')
.querySelector("h1").innerHTML;
browser.runtime.sendMessage({ type: "GOT_INFO_R", company, jobTitle });
let money = ""
const moneySectionNode = document.querySelector('section>section');
if (moneySectionNode && ["Base pay range", "Base pay"].includes(moneySectionNode.querySelector('h2').textContent)) {
money = moneySectionNode.querySelector("div>div>div").children[1]?.textContent ?? ''
}
browser.runtime.sendMessage({ type: "GOT_INFO_R", company, jobTitle, money });
} else if (message.type === "GOT_INFO_R") {
window.postMessage(message);
}
});
window.addEventListener("message", function (e) {
window.addEventListener("message", (e) => {
if (e.data.type === "MY_GET_URL") {
browser.runtime.sendMessage({ type: "MY_GET_URL" });
} else if (e.data.type === "HAS_EXTENSION_Q") {

View File

@ -32,6 +32,8 @@ export type Application = {
extra_data: string;
payrange: string;
status: number;
recruiter: string;
company: string;
flairs: Flair[];
};

View File

@ -4,10 +4,10 @@
let ready = $state(false);
const { children }: { children: Snippet } = $props();
const { children, redirect }: { children: Snippet, redirect: string } = $props();
onMount(() => {
ready = userStore.checkLogin();
ready = userStore.checkLogin(redirect);
});
</script>

View File

@ -22,23 +22,19 @@ function createUserStore() {
}
return {
checkLogin(redirect = true) {
console.log('test1');
checkLogin(redirect = "/login") {
if (user !== undefined) {
return true;
}
console.log('test2');
if (redirect) {
goto('/login');
goto(redirect);
}
console.log('test3');
return false;
},
get isLoggedIn() {
console.log(user);
return user !== undefined;
},

View File

@ -6,7 +6,7 @@
import AppliyedList from './AppliyedList.svelte';
</script>
<HasUser>
<HasUser redirect="/cv">
<div class="flex flex-col h-[100vh]">
<NavBar />
<div class="w-full px-4 grow h-full gap-3 flex flex-col">

View File

@ -23,7 +23,14 @@
return true;
}
const f = new RegExp(filter, 'ig');
return i.title.match(f);
let x = i.title;
if (i.company) {
x = `${x} @ ${i.company}`;
}
return x.match(f);
}) as item}
<div
class="card p-2 my-2 bg-slate-100"
@ -39,8 +46,16 @@
<div class:animate-pulse={applicationStore.dragging?.id === item.id}>
<h2 class="text-lg text-blue-500">
{item.title}
{#if item.company}
<div class="text-violet-800" >
@ {item.company}
</div>
{/if}
</h2>
<a href={item.url} class="text-violet-600 overflow-hidden whitespace-nowrap block">
<a
href={item.url}
class="text-violet-600 overflow-hidden whitespace-nowrap block"
>
{item.url}
</a>
</div>

View File

@ -1,5 +1,7 @@
<script lang="ts">
import { get } from '$lib/utils';
import { onMount } from 'svelte';
import Flair from '../flair/Flair.svelte';
let id: string | undefined | null;
@ -11,9 +13,39 @@
loadData();
});
const application = undefined;
type SimpleFlair = {
name: string;
description: string;
color: string;
};
async function loadData() {}
type Application = {
flairs: SimpleFlair[];
};
let application: Application | undefined = $state(undefined);
async function loadData() {
try {
application = await get(`application/cv/${id}`);
application.flairs.sort((a, b) => {
if (a.description && b.description) {
return 0;
}
if (a.description) {
return -1;
}
if (b.description) {
return 1;
}
return 0;
})
} catch (e) {
console.log('TODO show this to the user', e);
}
}
</script>
<svelte:head>
@ -21,10 +53,10 @@
</svelte:head>
<div class="flex items-center w-full flex-col">
<div class="max-w-[210mm] px-5 py-16">
<div class="py-10 w-[210mm]">
<div class="bg-white rounded-lg p-3">
<div class="w-full flex">
<h1 class="dark:text-white text-black text-5xl">Andre Henriques</h1>
<h1 class="text-black text-5xl">Andre Henriques</h1>
<div class="flex-grow"></div>
<div class="text-right">
<ul>
@ -34,9 +66,7 @@
>
</li>
<li class="px-1">
<a class="underline" href="tel:+4407823391342"
>+44 0 782339 1342</a
>
<a class="underline" href="tel:+4407823391342">+44 0 782339 1342</a>
</li>
</ul>
</div>
@ -56,42 +86,62 @@
</div>
</div>
</div>
{#if application}
<div class="p-5"></div>
<div>TODO: Application Information</div>
{/if}
<div class="p-5"></div>
<div class="text-black bg-white p-4 rounded-lg">
<h2 class="pb-2 text-3xl">Work Expericence</h2>
<div>
<div>
<h1>Senior Software Developer @ Planum Solucoes</h1>
<div class="ml-5">
<h2>4 year - May 2020 - Present</h2>
<h3>Working with:</h3>
<ul class="pl-5 list-disc">
<li>Python</li>
<li>Jenkins</li>
<li>GitLab CI</li>
<li>Ansible</li>
<li>Docker</li>
</ul>
<h3>Associated Software Developer / DevOps Engineer:</h3>
<ul class="pl-5 list-disc">
<li>Developed web-based tools for the DevOps team to use</li>
<li>
Updated Jenkins pipelines that the team uses to manage one of the
most important pipelines that the team manages.
</li>
<li>
Created new scripts that were used to clean up multiple terabytes of
unused data that led to improvements in the performance of the other
scripts running on the same server as well as the performance of the
backup system
</li>
</ul>
</div>
{#if application}
<h2 class="text-white p-3 text-4xl">
👋 Hello
{#if application.recruiter}
<span class="font-bold">{application.recruiter}</span> @ <span class="font-bold">{application.company}</span>
{:else if application.company}
recruiter @ <span class="font-bold">{application.company}</span>
{/if}
</h2>
<div class="p-3 bg-white w-[210mm] rounded-lg">
<h1>Your Ad / My skills</h1>
<div class="flex flex-wrap gap-2 py-2">
{#each application.flairs as flair}
<div class="min-w-0 {flair.description ? 'flex-grow w-full' : ''}">
<div class="p-2 rounded-lg" style="background: {flair.color};">
{flair.name}
{#if flair.description}
<div class="bg-white my-1 p-1 rounded-md">{flair.description}</div>
{/if}
</div>
</div>
</div>
{/each}
</div>
</div>
{/if}
<div class="p-5"></div>
<div class="px-5 w-[210mm]">
<h2 class="pb-2 text-4xl font-bold text-white">Work Expericence</h2>
</div>
<div class="p-2"></div>
<div class="w-[100vw] flex items-center flex-col">
<div class="p-3 bg-white w-[210mm] rounded-lg">
<h1>Senior Software Developer @ Planum Solucoes</h1>
<div class="ml-5">
<h2>4 year - May 2020 - Present</h2>
<h3>Developed various projects:</h3>
<ul class="pl-5 list-disc">
<li>Developing various websites using React and Svelte.</li>
<li>Interacting with a RESTful API</li>
<li>Implemented an ORM system using Java Reflection</li>
<li>Implemented automatic deployment with GitLab CI/CD tools.</li>
<li>Linux Server Administration</li>
<li>Technologies used: React, WebRTC, WebSockets, Rest, Google Maps AP</li>
</ul>
</div>
</div>
</div>
<div class="p-2"></div>
<div class="w-[100vw] flex items-center flex-col">
<div class="text-black w-[210mm] bg-white p-4 rounded-lg">
<div>
<div>
<h1>Associate Devops Engineer @ Sky UK</h1>
<div class="ml-5">
@ -122,21 +172,24 @@
</div>
</div>
</div>
<div class="p-5"></div>
<div class="bg-white p-3 text-black rounded-lg">
<h2 class="pb-2 text-3xl">Education</h2>
</div>
<div class="p-5"></div>
<div class="bg-white p-3 text-black rounded-lg w-[210mm]">
<h2 class="pb-2 text-3xl">Education</h2>
<div>
<div>
<div>
<h1>University of Surrey</h1>
<div class="ml-5">
<h2>July 2020 - June 2024</h2>
</div>
<h1>University of Surrey</h1>
<div class="ml-5">
<h2>July 2020 - June 2024</h2>
</div>
</div>
</div>
<!--div class="p-5"></div>
<div>TODO: Previous projetcs</div -->
<!-- div class="p-5"></div>
<div>TODO: Info form</div -->
</div>
<!--div class="p-5"></div>
<div>TODO: Previous projetcs</div -->
<!-- div class="p-5"></div>
<div>TODO: Info form</div -->
</div>

View File

@ -0,0 +1,22 @@
<script lang="ts">
import { applicationStore } from '$lib/ApplicationsStore.svelte';
import { preventDefault } from '$lib/utils';
import type { Snippet } from 'svelte';
let { ondrop, icon, children }: { ondrop: () => void, icon: string, children: Snippet } = $props();
</script>
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
{ondrop}
>
<span
class="bi bi-{icon} text-7xl absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-{icon} text-7xl opacity-0"></span>
<div class="text-xl">{@render children()}</div>
</div>

View File

@ -11,6 +11,7 @@
import ExtractTextDialog from './ExtractTextDialog.svelte';
import Flair from '../flair/Flair.svelte';
import NewUrlDialog from './NewUrlDialog.svelte';
import DropZone from './DropZone.svelte';
import { userStore } from '$lib/UserStore.svelte';
let activeItem: Application | undefined = $state();
@ -42,6 +43,7 @@
if (item.status === 0) {
openWindow(item.url);
openCV(item.id);
}
try {
@ -76,6 +78,14 @@
);
}
function openCV(id?: string) {
window.open(
`/cv?id=${id ?? activeItem!.id}`,
'cv viwer',
`location,height=${window.screen.height},width=${window.screen.width},scrollbars,status,toolbar,menubar,popup`
);
}
async function loadActive() {
try {
activeItem = await get('application/active');
@ -107,7 +117,9 @@
function setExtData() {
if (!lastExtData || !activeItem) return;
activeItem.title = `${lastExtData.company} - ${lastExtData.jobTitle}`;
activeItem.title = lastExtData.jobTitle;
activeItem.company = lastExtData.company;
activeItem.payrange = lastExtData.money;
window.requestAnimationFrame(() => {
save();
lastExtData = undefined;
@ -203,6 +215,16 @@
{statusMapping}
</div>
{/if}
<div class="flex gap-2">
<fieldset class="grow">
<label class="flabel" for="title">Company</label>
<input class="finput" id="title" bind:value={activeItem.company} onchange={save} />
</fieldset>
<fieldset class="grow">
<label class="flabel" for="title">Recruiter</label>
<input class="finput" id="title" bind:value={activeItem.recruiter} onchange={save} />
</fieldset>
</div>
<fieldset>
<label class="flabel" for="title">Title</label>
<input class="finput" id="title" bind:value={activeItem.title} onchange={save} />
@ -262,10 +284,7 @@
>
Open
</button>
<button
class="btn-primary"
onclick={() => window.open(`/cv?id=${activeItem!.id}`, '_blank')}
>
<button class="btn-primary" onclick={openCV}>
Open CV
</button>
<button class="btn-primary" onclick={() => extractTokens.showModal()}>
@ -284,100 +303,49 @@
</div>
{#if applicationStore.dragging}
<div class="flex w-full flex-grow rounded-lg p-1 gap-2">
<!-- Do nothing -->
<DropZone icon="box-arrow-down" ondrop={() => {
moveStatus(ApplicationStatus.ToApply);
}}>
To apply
</DropZone>
<!-- Ignore -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={() => {
moveStatus(ApplicationStatus.Ignore);
}}
>
<span
class="bi bi-trash-fill text-7xl absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-trash-fill text-7xl opacity-0"></span>
<div class="text-xl">Drop Application Ignore it.</div>
</div>
<DropZone icon="trash-fill" ondrop={() => {
moveStatus(ApplicationStatus.Ignore);
}}>
Ignore it
</DropZone>
<!-- Expired -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={() => {
if (activeItem && activeItem.status === ApplicationStatus.Expired) {
moveStatus(ApplicationStatus.ToApply);
} else {
moveStatus(ApplicationStatus.Expired);
}
}}
>
<span
class="bi bi-clock-fill text-7xl text-orange-500 absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-clock-fill text-7xl text-orange-500 opacity-0"></span>
<div class="text-orange-500 text-xl">Mark as expired.</div>
</div>
<DropZone icon="clock-fill text-orange-500" ondrop={() => {
if (activeItem && activeItem.status === ApplicationStatus.Expired) {
moveStatus(ApplicationStatus.ToApply);
} else {
moveStatus(ApplicationStatus.Expired);
}
}}>
Mark as expired
</DropZone>
<!-- Repeated -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={() => {
remove();
}}
>
<span
class="bi bi-trash-fill text-7xl text-danger absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-trash-fill text-7xl text-danger opacity-0"></span>
<div class="text-danger text-xl">This is repeated</div>
</div>
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}>
Delete it
</DropZone>
<!-- Applyed -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={async () => {
await moveStatus(ApplicationStatus.Applyed);
applicationStore.loadAplyed(true);
}}
>
<span
class="bi bi-server text-7xl text-confirm absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-server text-7xl text-confirm opacity-0"></span>
<div class="text-confirm text-xl">Apply</div>
</div>
<DropZone icon="server text-confirm" ondrop={async () => {
await moveStatus(ApplicationStatus.Applyed);
applicationStore.loadAplyed(true);
}}>
Apply
</DropZone>
<!-- Rejected -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={() => {
moveStatus(ApplicationStatus.ApplyedButSaidNo);
}}
>
<span
class="bi bi-fire text-danger text-7xl absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-fire text-7xl text-danger opacity-0"></span>
<div class="text-danger text-xl">I was rejeted :(.</div>
</div>
<DropZone icon="fire text-danger" ondrop={() => moveStatus(ApplicationStatus.ApplyedButSaidNo)}>
I was rejeted :(
</DropZone>
</div>
{/if}
{:else}