added the views funcionality and other things

This commit is contained in:
Andre Henriques 2024-10-01 16:48:50 +01:00
parent 407b955950
commit 4aafb7e6f9
20 changed files with 2164 additions and 1609 deletions

View File

@ -1,6 +1,37 @@
# vi: ft=dockerfile
FROM docker.io/nginx
FROM docker.io/node:22-alpine3.19 as build
WORKDIR /app
ADD site .
RUN npm install
RUN npm run build
FROM docker.io/nginx:1.27.1-alpine
RUN apk add openjdk17
RUN mkdir /app
WORKDIR /app
ADD api/gradlew .
ADD api/gradle gradle
RUN ./gradlew
ADD nginx.proxy.conf /nginx.conf
CMD ["nginx", "-c", "/nginx.conf", "-g", "daemon off;"]
RUN mkdir -p /www/page
ADD api /app
RUN ./gradlew bootJar
ADD entrypoint.sh /usr/bin
COPY --from=build /app/build/ /www/page/
ENTRYPOINT ["entrypoint.sh"]

View File

@ -33,7 +33,10 @@ dependencies {
implementation("org.bouncycastle:bcprov-jdk18on:1.76")
}
//kotlin { compilerOptions { freeCompilerArgs.addAll("-Xjsr305=strict") } compiler { jvm { target = JavaLanguageVersion.of(17) } } }
springBoot {
mainClass.set("com.andr3h3nriqu3s.applications.ApplicationsApplicationKt")
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")

View File

@ -9,3 +9,4 @@ class ApplicationsApplication
fun main(args: Array<String>) {
runApplication<ApplicationsApplication>(*args)
}

View File

@ -3,6 +3,7 @@ package com.andr3h3nriqu3s.applications
import java.sql.ResultSet
import java.util.UUID
import kotlin.collections.setOf
import kotlin.collections.emptyList
import okhttp3.OkHttpClient
import okhttp3.Request
import org.springframework.http.MediaType
@ -32,7 +33,9 @@ data class Application(
var status: Int,
var company: String,
var recruiter: String,
var message: String,
var flairs: List<Flair>,
var views: List<View>,
) {
companion object : RowMapper<Application> {
override public fun mapRow(rs: ResultSet, rowNum: Int): Application {
@ -48,7 +51,9 @@ data class Application(
rs.getInt("status"),
rs.getString("company"),
rs.getString("recruiter"),
emptyList()
rs.getString("message"),
emptyList(),
emptyList(),
)
}
}
@ -64,7 +69,7 @@ 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>)
data class CVData(val company: String, val recruiter: String, val message: String, val flairs: List<SimpleFlair>)
@RestController
@ControllerAdvice
@ -72,24 +77,28 @@ data class CVData(val company: String, val recruiter: String, val flairs: List<
class ApplicationsController(
val sessionService: SessionService,
val applicationService: ApplicationService,
val flairService: FlairService
val flairService: FlairService,
val viewService: ViewService,
) {
@GetMapping(path = ["/cv/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun getCV(
@PathVariable id: String,
@RequestHeader("token") token: String
@RequestHeader("token") token: String?
): CVData? {
print("here!");
val user = sessionService.verifyToken(token);
val application = applicationService.findApplicationByIdNoUser(id);
if (application == null) return null;
if (user == null) {
viewService.create(application.id)
}
val flairs = application.flairs.map {it.toFlairSimple()};
return CVData(application.company, application.recruiter, flairs);
return CVData(application.company, application.recruiter, application.message, flairs);
}
@PostMapping(path = ["/text"], produces = [MediaType.APPLICATION_JSON_VALUE])
@ -204,7 +213,9 @@ class ApplicationsController(
0,
"",
"",
emptyList()
"",
emptyList(),
emptyList(),
)
}
@ -408,7 +419,7 @@ class ApplicationsController(
}
@Service
class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
class ApplicationService(val db: JdbcTemplate, val flairService: FlairService, val viewService: ViewService) {
public fun findApplicationByUrl(user: UserDb, url: String, unique_url: String?): Application? {
if (unique_url != null) {
@ -456,6 +467,7 @@ class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
var application = applications[0]
application.flairs = flairService.listFromLinkApplicationId(application.id)
application.views = viewService.listFromApplicationId(application.id)
return application
}
@ -469,6 +481,7 @@ class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
var application = applications[0]
// Views are not needed for this request
application.flairs = flairService.listFromLinkApplicationId(application.id)
return application
@ -480,7 +493,7 @@ class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
}
db.update(
"insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
"insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status, company, recruiter, message) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
application.id,
application.url,
application.original_url,
@ -491,7 +504,8 @@ class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
application.payrange,
application.status,
application.company,
application.recruiter
application.recruiter,
application.message,
)
return true
@ -517,7 +531,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=?, company=?, recruiter=? where id=?",
"update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, status=?, company=?, recruiter=?, message=? where id=?",
application.url,
application.original_url,
application.unique_url,
@ -528,6 +542,7 @@ class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
application.status,
application.company,
application.recruiter,
application.message,
application.id,
)
return application

View File

@ -3,19 +3,16 @@ package com.andr3h3nriqu3s.applications
import java.util.UUID
import kotlin.io.encoding.Base64
import kotlin.random.Random
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
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.ResponseStatus
import org.springframework.web.bind.annotation.RestController
enum class UserLevel(val level: Int) {
@ -167,7 +164,10 @@ data class Session(val token: String, val user_id: String)
@Service
class SessionService(val db: JdbcTemplate) {
fun verifyToken(token: String): UserDb? {
fun verifyToken(token: String?): UserDb? {
if (token == null) {
return null;
}
var users =
db
.query(

View File

@ -0,0 +1,71 @@
package com.andr3h3nriqu3s.applications
import java.sql.ResultSet
import java.util.Date
import java.util.UUID
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Service
data class View(var id: String, var application_id: String, var time: Date) {
companion object : RowMapper<View> {
override public fun mapRow(rs: ResultSet, rowNum: Int): View {
return View(
rs.getString("id"),
rs.getString("application_id"),
rs.getDate("time"),
)
}
}
}
@Service
public class ViewService(val db: JdbcTemplate) {
public fun listFromApplicationId(id: String): List<View> =
db.query("select * from views where application_id=?;", arrayOf(id), View).toList()
public fun getById(id: String): View? {
val items = db.query("select * from views where id=?;", arrayOf(id), View).toList()
if (items.size == 0) {
return null
}
return items[0]
}
public fun deleteById(id: String): View {
val view = this.getById(id)
if (view == null) {
throw NotFound()
}
db.update("delete from views where id=?", id)
return view
}
public fun update(view: View): View {
db.update(
"update views set application_id=?, time=? where id=?;",
view.application_id,
view.time,
view.id,
)
return view
}
public fun create(application_id: String): View {
val id = UUID.randomUUID().toString()
var new_view = View(id, application_id, Date())
db.update(
"insert into views (id, application_id) values (?, ?)",
new_view.id,
new_view.application_id
)
return new_view
}
}

View File

@ -19,18 +19,25 @@ create table if not exists applications (
company text,
recruiter text,
title text,
mesasge text default '',
user_id text,
extra_data text,
status integer
);
create table if not exists views (
id text primary key,
application_id text not null,
time timestamp default current_timestamp
);
create table if not exists flair (
id text primary key,
user_id text not null,
color text default '#ff0000',
name text default 'New Flair',
expr text default 'flair'
description text default '',
expr text default 'flair',
description text default ''
);
create table if not exists flair_link (

7
entrypoint.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
cd /app
./gradlew bootRun &
nginx -c /nginx.conf -g "daemon off;"

View File

@ -3,11 +3,12 @@ events {
}
http {
proxy_read_timeout 600;
proxy_connect_timeout 600;
proxy_send_timeout 600;
include mime.types;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
@ -19,11 +20,7 @@ http {
client_max_body_size 5G;
location / {
proxy_http_version 1.1;
proxy_pass http://localhost:5173;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
root /www/page;
}
location /api {
@ -34,16 +31,5 @@ http {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location /glassdoor {
rewrite ^/glassdoor(.*)$ $1 break;
proxy_http_version 1.1;
proxy_pass https://glassdoor.com;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
}

View File

@ -32,5 +32,8 @@
"typescript-eslint": "^8.0.0-alpha.20",
"vite": "^5.0.3"
},
"type": "module"
"type": "module",
"dependencies": {
"@sveltejs/adapter-static": "^3.0.5"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -72,3 +72,19 @@ @layer components {
@apply bg-white rounded-lg drop-shadow-lg;
}
}
@print {
@page :footer {
display: none
}
@page :header {
display: none
}
}
@page {
size: auto;
margin: 0;
}

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="en" moznomarginboxes mozdisallowselectionprint>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />

View File

@ -22,6 +22,12 @@ export const ApplicationStatusMaping: Record<
5: 'Expired'
});
export type View = {
id: string,
application_id: string,
time: string,
}
export type Application = {
id: string;
url: string;
@ -35,6 +41,8 @@ export type Application = {
recruiter: string;
company: string;
flairs: Flair[];
message: string;
views: View[];
};
function createApplicationStore() {

View File

@ -10,7 +10,7 @@
<div class="flex flex-col h-[100vh]">
<NavBar />
<div class="w-full px-4 grow h-full gap-3 flex flex-col">
<div class="flex h-3/5 gap-3">
<div class="flex h-4/5 gap-3">
<ApplicationsList />
<WorkArea />
</div>

View File

@ -1,7 +1,6 @@
<script lang="ts">
import { get } from '$lib/utils';
import { onMount } from 'svelte';
import Flair from '../flair/Flair.svelte';
let id: string | undefined | null;
@ -20,15 +19,23 @@
};
type Application = {
recruiter: string;
message: string;
company: string;
flairs: SimpleFlair[];
};
let application: Application | undefined = $state(undefined);
async function loadData() {
if (!id) return;
try {
application = await get(`application/cv/${id}`);
if (!application) {
return;
}
application.flairs.sort((a, b) => {
if (a.description && b.description) {
return 0;
@ -40,8 +47,7 @@
return 1;
}
return 0;
})
});
} catch (e) {
console.log('TODO show this to the user', e);
}
@ -53,7 +59,7 @@
</svelte:head>
<div class="flex items-center w-full flex-col">
<div class="py-10 w-[210mm]">
<div class="py-10 w-[190mm]">
<div class="bg-white rounded-lg p-3">
<div class="w-full flex">
<h1 class="text-black text-5xl">Andre Henriques</h1>
@ -61,6 +67,13 @@
<div class="text-right">
<ul>
<li class="px-1">
{#if id}
<a class="underline" href="https://www.andr3h3nriqu3s.com/cv?id={id}">andr3h3nriqu3s.com</a>
{:else}
<a class="underline" href="https://www.andr3h3nriqu3s.com/cv">andr3h3nriqu3s.com</a>
{/if}
</li>
<li class="px-1">
<a class="underline" href="mailto:contact@andr3h3nriqu3s.com"
>contact@andr3h3nriqu3s.com</a
>
@ -91,36 +104,69 @@
<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>
<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>
{#if application.message}
<div class="p-3 bg-white w-[190mm] rounded-lg">
<h1>A small message from me</h1>
<div class="py-2">
{@html application.message.split('\n').join('<br>')}
</div>
</div>
<div class="p-1"></div>
{/if}
{#if application.flairs.length > 0}
<div class="p-3 bg-white w-[190mm] 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
class="p-2 rounded-lg forced-color-adjust-none"
style="background: {flair.color};"
>
<div
class="rounded-lg print:p-2 print:inline-block"
style="background: {flair.color}; print-color-adjust: exact !important;"
>
{flair.name}
</div>
<span class="hidden print:inline">:</span>
<div class="bg-white my-1 print:inline p-1 rounded-md">
{flair.description}
</div>
</div>
{:else}
<div
class="p-2 rounded-lg forced-color-adjust-none"
style="background: {flair.color}; print-color-adjust: exact !important;"
>
{flair.name}
</div>
{/if}
</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>
{/if}
<div class="p-2"></div>
<div class="w-[190mm]">
<h2 class="pb-2 px-2 print:px-5 text-4xl print:text-3xl 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">
<div class="p-3 bg-white w-[190mm] rounded-lg">
<h1>Senior Software Developer @ Planum Solucoes</h1>
<div class="ml-5">
<h2>4 year - May 2020 - Present</h2>
@ -140,7 +186,7 @@
<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 class="text-black w-[190mm] bg-white p-4 rounded-lg">
<div>
<div>
<h1>Associate Devops Engineer @ Sky UK</h1>
@ -176,11 +222,11 @@
<div class="p-5"></div>
<div class="bg-white p-3 text-black rounded-lg w-[210mm]">
<div class="bg-white p-3 text-black rounded-lg w-[190mm]">
<h2 class="pb-2 text-3xl">Education</h2>
<div>
<div>
<h1>University of Surrey</h1>
<h1>Bachelors of science in Computer Science @ University of Surrey</h1>
<div class="ml-5">
<h2>July 2020 - June 2024</h2>
</div>

View File

@ -85,7 +85,9 @@
Expr: {item.expr}
</div>
<div class="flex-grow"></div>
<button class="btn-danger" onclick={preventDefault(() => remove(item.id))}> Remove </button>
<button class="btn-danger" onclick={preventDefault(() => remove(item.id))}>
Remove
</button>
</div>
{#if item.id == edit?.id}
<form onsubmit={preventDefault(update)} class="shadow-inner p-2 rounded-xl">
@ -103,7 +105,11 @@
</fieldset>
<fieldset>
<label class="flabel" for="description">Description</label>
<textarea required id="description" class="finput w-full" bind:value={edit.description}
<textarea
required
id="description"
class="finput w-full"
bind:value={edit.description}
></textarea>
</fieldset>
<div class="btns w-full">

View File

@ -218,20 +218,40 @@
<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} />
<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} />
<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} />
<input
class="finput"
id="title"
bind:value={activeItem.title}
onchange={save}
/>
</fieldset>
<fieldset>
<label class="flabel" for="payrange">Pay Range</label>
<input class="finput" id="payrange" bind:value={activeItem.payrange} onchange={save} />
<input
class="finput"
id="payrange"
bind:value={activeItem.payrange}
onchange={save}
/>
</fieldset>
<fieldset
draggable="false"
@ -271,9 +291,31 @@
</div>
<fieldset>
<label class="flabel" for="extra">Extra Info</label>
<textarea class="finput" id="extra" bind:value={activeItem.extra_data} onchange={save}
<textarea
class="finput"
id="extra"
bind:value={activeItem.extra_data}
onchange={save}
></textarea>
</fieldset>
<fieldset>
<label class="flabel" for="extra">Message</label>
<textarea
draggable={false}
class="finput"
id="extra"
bind:value={activeItem.message}
onchange={save}
></textarea>
</fieldset>
{#if activeItem.views.length > 0}
<h1 class="text-sm">Non Loggedin Views Time ({activeItem.views.length})</h1>
{#each activeItem.views as view}
<div>
{view.time}
</div>
{/each}
{/if}
</div>
<div class="flex btns">
<button
@ -284,9 +326,7 @@
>
Open
</button>
<button class="btn-primary" onclick={openCV}>
Open CV
</button>
<button class="btn-primary" onclick={() => openCV()}> Open CV </button>
<button class="btn-primary" onclick={() => extractTokens.showModal()}>
Extract Flair
</button>
@ -294,7 +334,9 @@
<button class="btn-primary" onclick={() => setExtData()}> Ext Data </button>
{/if}
{#if activeItem.original_url == null}
<button class="btn-primary" onclick={() => changeUrl.showModal()}> Update Url </button>
<button class="btn-primary" onclick={() => changeUrl.showModal()}>
Update Url
</button>
{/if}
{#if activeItem.original_url != null}
<button class="btn-danger" onclick={resetUrl}> Reset Url </button>
@ -303,47 +345,59 @@
</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={() => {
<DropZone
icon="box-arrow-down"
ondrop={() => {
moveStatus(ApplicationStatus.ToApply);
}}>
}}
>
To apply
</DropZone>
<!-- Ignore -->
<DropZone icon="trash-fill" ondrop={() => {
<DropZone
icon="trash-fill"
ondrop={() => {
moveStatus(ApplicationStatus.Ignore);
}}>
}}
>
Ignore it
</DropZone>
<!-- Expired -->
<DropZone icon="clock-fill text-orange-500" ondrop={() => {
<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 -->
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}>
Delete it
</DropZone>
<DropZone icon="trash-fill text-danger" ondrop={() => remove()}>Delete it</DropZone>
<!-- Applyed -->
<DropZone icon="server text-confirm" ondrop={async () => {
<DropZone
icon="server text-confirm"
ondrop={async () => {
await moveStatus(ApplicationStatus.Applyed);
applicationStore.loadAplyed(true);
}}>
}}
>
Apply
</DropZone>
<!-- Rejected -->
<DropZone icon="fire text-danger" ondrop={() => moveStatus(ApplicationStatus.ApplyedButSaidNo)}>
<DropZone
icon="fire text-danger"
ondrop={() => moveStatus(ApplicationStatus.ApplyedButSaidNo)}
>
I was rejeted :(
</DropZone>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,4 +1,4 @@
import adapter from '@sveltejs/adapter-auto';
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */