Backend: Rewrote the entire database structure to comply with languages needs. Frontend: Adapted new database parsing. Removed some log.

This commit is contained in:
Yohan Boujon 2024-02-04 21:23:51 +01:00
parent a8c78bc7b0
commit 437f2a8a06
18 changed files with 312 additions and 165 deletions

View file

@ -6,12 +6,8 @@
CREATE TABLE public.info (
birth_year date NULL,
id serial4 NOT NULL,
full_name text NULL,
full_name text NOT NULL,
phone_number varchar NULL,
email varchar NULL,
softskills text NULL,
interests text NULL,
profile_pic text NULL,
CONSTRAINT info_pkey PRIMARY KEY (id)
profile_pic text NULL
);

View file

@ -6,10 +6,9 @@
CREATE TABLE public.languages (
id serial4 NOT NULL,
info_id int4 NOT NULL,
lang text NOT NULL,
icon_alpha varchar(3) NOT NULL,
"level" text NOT NULL,
CONSTRAINT languages_pkey PRIMARY KEY (id),
CONSTRAINT languages_fk FOREIGN KEY (info_id) REFERENCES public.info(id)
url_name varchar(2) NOT NULL,
CONSTRAINT languages_pkey PRIMARY KEY (id)
);

View file

@ -6,10 +6,7 @@
CREATE TABLE public.experience (
id serial4 NOT NULL,
job_position text NULL,
job_description text NULL,
enterprise text NULL,
enterprise_location text NULL,
start_year date NULL,
end_year date NULL,
picture_url text NULL,

View file

@ -7,16 +7,11 @@
CREATE TABLE public.project (
id serial4 NOT NULL,
date_done date NULL,
title text NULL,
description text NULL,
github_link text NULL,
info_id int4 NOT NULL,
picture_name text NULL,
type_project text NULL,
report_link text NULL,
archive_link text NULL,
app_link text NULL,
short_description varchar(100) NULL,
CONSTRAINT project_pkey PRIMARY KEY (id),
CONSTRAINT project_fk FOREIGN KEY (info_id) REFERENCES public.info(id)
CONSTRAINT project_pk PRIMARY KEY (id)
);

View file

@ -0,0 +1,15 @@
-- public.education_text definition
-- Drop table
-- DROP TABLE public.education_text;
CREATE TABLE public.education_text (
education_id serial4 NOT NULL,
lang_id serial4 NOT NULL,
speciality text NULL,
school_location text NULL,
school_options text NULL,
CONSTRAINT education_text_education_fk FOREIGN KEY (education_id) REFERENCES public.education(id),
CONSTRAINT education_text_languages_fk FOREIGN KEY (lang_id) REFERENCES public.languages(id)
);

View file

@ -0,0 +1,15 @@
-- public.experience_text definition
-- Drop table
-- DROP TABLE public.experience_text;
CREATE TABLE public.experience_text (
experience_id int4 NOT NULL,
lang_id serial4 NOT NULL,
job_position text NULL,
job_description text NULL,
enterprise_location text NULL,
CONSTRAINT experience_text_experience_fk FOREIGN KEY (experience_id) REFERENCES public.experience(id),
CONSTRAINT experience_text_languages_fk FOREIGN KEY (lang_id) REFERENCES public.languages(id)
);

View file

@ -0,0 +1,15 @@
-- public.project_text definition
-- Drop table
-- DROP TABLE public.project_text;
CREATE TABLE public.project_text (
project_id int4 NOT NULL,
lang_id int4 NOT NULL,
title text NULL,
description text NULL,
short_description varchar(100) NULL,
CONSTRAINT project_text_languages_fk FOREIGN KEY (lang_id) REFERENCES public.languages(id),
CONSTRAINT project_text_project_fk FOREIGN KEY (project_id) REFERENCES public.project(id)
);

12
backend/db/4_skills.sql Normal file
View file

@ -0,0 +1,12 @@
-- public.skills definition
-- Drop table
-- DROP TABLE public.skills;
CREATE TABLE public.skills (
softskills text NULL,
interests text NULL,
lang_id serial4 NOT NULL,
CONSTRAINT skills_languages_fk FOREIGN KEY (lang_id) REFERENCES public.languages(id)
);

View file

@ -6,11 +6,9 @@
CREATE TABLE public.programming_languages (
id serial4 NOT NULL,
info_id int4 NOT NULL,
lang text NOT NULL,
icon text NOT NULL,
type_icon text NOT NULL,
color varchar(7) NULL,
CONSTRAINT programming_languages_pkey PRIMARY KEY (id),
CONSTRAINT programming_languages_fk FOREIGN KEY (info_id) REFERENCES public.info(id)
CONSTRAINT programming_languages_pkey PRIMARY KEY (id)
);

View file

@ -6,7 +6,6 @@
CREATE TABLE public.softwares (
id serial4 NOT NULL,
info_id int4 NOT NULL,
software text NOT NULL,
icon text NOT NULL,
type_icon text NOT NULL,

View file

@ -8,7 +8,7 @@ CREATE TABLE public.project_tags (
project_id int4 NOT NULL,
programming_languages_id int4 NULL,
softwares_id int4 NULL,
CONSTRAINT project_tags_project_fk FOREIGN KEY (project_id) REFERENCES public.project(id),
CONSTRAINT project_tags_programming_languages_fk FOREIGN KEY (programming_languages_id) REFERENCES public.programming_languages(id),
CONSTRAINT project_tags_project_fk FOREIGN KEY (project_id) REFERENCES public.project(id),
CONSTRAINT project_tags_softwares_fk FOREIGN KEY (softwares_id) REFERENCES public.softwares(id)
);

View file

@ -1,40 +1,42 @@
use serde::{Deserialize, Serialize};
use sqlx::types::chrono::NaiveDate;
#[derive(Deserialize, Serialize, Copy, Clone)]
pub struct LangId {
pub id: Option<i32>
}
#[derive(Deserialize, Serialize)]
pub struct Info {
pub id: i64,
pub full_name: Option<String>,
pub phone_number: Option<String>,
pub email: Option<String>,
pub softskills: Option<String>,
pub interests: Option<String>,
pub birth_year: Option<NaiveDate>,
pub profile_pic: Option<String>
pub profile_pic: Option<String>,
pub softskills: Option<String>,
pub interests: Option<String>
}
#[derive(Deserialize, Serialize)]
pub struct Education {
pub id: i64,
pub school: Option<String>,
pub start_year: Option<NaiveDate>,
pub end_year: Option<NaiveDate>,
pub school: Option<String>,
pub picture_url: Option<String>,
pub speciality: Option<String>,
pub school_location: Option<String>,
pub school_options: Option<String>,
pub picture_url: Option<String>
pub school_location: Option<String>
}
#[derive(Deserialize, Serialize)]
pub struct Experience {
pub id: i64,
pub job_position: Option<String>,
pub job_description: Option<String>,
pub enterprise: Option<String>,
pub enterprise_location: Option<String>,
pub start_year: Option<NaiveDate>,
pub end_year: Option<NaiveDate>,
pub picture_url: Option<String>
pub picture_url: Option<String>,
pub job_position: Option<String>,
pub job_description: Option<String>,
pub enterprise_location: Option<String>
}
#[derive(Deserialize, Serialize)]
@ -54,25 +56,25 @@ pub struct Project {
#[derive(Deserialize, Serialize)]
pub struct ProgrammingLanguages {
pub lang: String,
pub icon: String,
pub type_icon: String,
pub lang: Option<String>,
pub icon: Option<String>,
pub type_icon: Option<String>,
pub color: Option<String>
}
#[derive(Deserialize, Serialize)]
pub struct Softwares {
pub software: String,
pub icon: String,
pub type_icon: String,
pub software: Option<String>,
pub icon: Option<String>,
pub type_icon: Option<String>,
pub color: Option<String>
}
#[derive(Deserialize, Serialize)]
pub struct Languages {
pub lang: String,
pub icon_alpha: String,
pub level: String
pub lang: Option<String>,
pub icon_alpha: Option<String>,
pub level: Option<String>
}
#[derive(Deserialize, Serialize)]

View file

@ -11,8 +11,8 @@ use tower_http::cors::CorsLayer;
mod db;
use db::{
AllTags, Education, Experience, Info, Languages, ProgrammingLanguages, Project, SimpleProject,
Softwares, Tags,
AllTags, Education, Experience, Info, LangId, Languages, ProgrammingLanguages, Project,
SimpleProject, Softwares, Tags,
};
#[tokio::main]
@ -27,18 +27,20 @@ async fn main() -> Result<()> {
// Creating a router
let router = Router::new()
.route("/info", get(info))
.route("/education", get(education))
.route("/experience", get(experience))
.route("/skills/:id", get(skills))
.route("/tags/:info_id/:project_id", get(tags))
.route("/tags/:id", get(alltags))
.route("/get_lang_id/:lang", get(get_lang_id))
.route("/info/:lang_id", get(info))
.route("/education/:lang_id", get(education))
.route("/experience/:lang_id", get(experience))
.route("/project/:lang_id", get(projects))
.route("/hard_skills", get(hard_skills))
.route("/tags/:project_id", get(tags))
.route("/tags", get(alltags))
.route(
"/getproject_programming/:programming_id",
"/getproject_programming/:programming_id/:lang_id",
get(getproject_programming),
)
.route(
"/getproject_software/:software_id",
"/getproject_software/:software_id/:lang_id",
get(getproject_software),
)
.with_state(pool)
@ -51,167 +53,245 @@ async fn main() -> Result<()> {
.await?)
}
macro_rules! _gather_data {
($data_type:ty, $sql_cmd:expr, $pool:expr) => {
sqlx::query_as!($data_type, $sql_cmd).fetch_all($pool).await
};
async fn get_lang_id(Path(lang): Path<String>, State(pool): State<PgPool>) -> Json<LangId> {
let datas = sqlx::query_as!(
LangId,
"SELECT id
FROM public.languages l
WHERE l.url_name = $1;",
lang
)
.fetch_all(&pool)
.await
.unwrap();
if datas.len() > 0 {
Json(datas[0])
} else {
Json(LangId { id: Option::None })
}
}
async fn info(State(pool): State<PgPool>) -> Json<Vec<Info>> {
async fn info(Path(lang_id): Path<i32>, State(pool): State<PgPool>) -> Json<Vec<Info>> {
let datas = sqlx::query_as!(
Info,
"SELECT id, full_name, phone_number, email, softskills, interests, birth_year, profile_pic FROM public.info"
"SELECT
(SELECT full_name FROM public.info) AS full_name,
(SELECT phone_number FROM public.info LIMIT 1) AS phone_number,
(SELECT email FROM public.info LIMIT 1) AS email,
(SELECT birth_year FROM public.info LIMIT 1) AS birth_year,
(SELECT profile_pic FROM public.info LIMIT 1) AS profile_pic,
(SELECT softskills FROM public.skills WHERE lang_id = $1 LIMIT 1) AS softskills,
(SELECT interests FROM public.skills WHERE lang_id = $1 LIMIT 1) AS interests;",
lang_id
)
.fetch_all(&pool)
.await.unwrap_or(vec![]);
.await
.unwrap();
Json(datas)
}
async fn education(State(pool): State<PgPool>) -> Json<Vec<Education>> {
let datas = sqlx::query_as!(Education, "SELECT * FROM public.education")
.fetch_all(&pool)
.await
.unwrap_or(vec![]);
async fn education(Path(lang_id): Path<i32>, State(pool): State<PgPool>) -> Json<Vec<Education>> {
let datas = sqlx::query_as!(
Education,
"SELECT
e.school,
e.start_year,
e.end_year,
e.picture_url,
MAX(CASE WHEN et.lang_id = $1 THEN et.speciality END) AS speciality,
MAX(CASE WHEN et.lang_id = $1 THEN et.school_options END) AS school_options,
MAX(CASE WHEN et.lang_id = $1 THEN et.school_location END) AS school_location
FROM public.education e
JOIN public.education_text et ON e.id = et.education_id
GROUP BY e.school, e.start_year, e.end_year, e.picture_url;
",
lang_id
)
.fetch_all(&pool)
.await
.unwrap();
Json(datas)
}
async fn experience(State(pool): State<PgPool>) -> Json<Vec<Experience>> {
let datas = sqlx::query_as!(Experience, "SELECT * FROM public.experience")
.fetch_all(&pool)
.await
.unwrap_or(vec![]);
async fn experience(Path(lang_id): Path<i32>, State(pool): State<PgPool>) -> Json<Vec<Experience>> {
let datas = sqlx::query_as!(
Experience,
"SELECT
e.enterprise,
e.start_year,
e.end_year,
e.picture_url,
MAX(CASE WHEN et.lang_id = $1 THEN et.job_position END) AS job_position,
MAX(CASE WHEN et.lang_id = $1 THEN et.job_description END) AS job_description,
MAX(CASE WHEN et.lang_id = $1 THEN et.enterprise_location END) AS enterprise_location
FROM public.experience e
JOIN public.experience_text et ON e.id = et.experience_id
GROUP BY e.enterprise, e.start_year, e.end_year, e.picture_url;
",
lang_id
)
.fetch_all(&pool)
.await
.unwrap();
Json(datas)
}
async fn skills(
Path(id): Path<i32>,
State(pool): State<PgPool>,
) -> Json<(
Vec<Project>,
Vec<ProgrammingLanguages>,
Vec<Softwares>,
Vec<Languages>,
)> {
let project = sqlx::query_as!(
async fn projects(Path(lang_id): Path<i32>, State(pool): State<PgPool>) -> Json<Vec<Project>> {
let datas = sqlx::query_as!(
Project,
"SELECT id, date_done, title, description, github_link, picture_name, type_project, report_link, archive_link, app_link, short_description FROM public.project WHERE project.info_id = $1 ORDER BY date_done DESC",
id
"SELECT
p.id,
p.date_done,
p.picture_name,
p.type_project,
p.github_link,
p.report_link,
p.archive_link,
p.app_link,
MAX(CASE WHEN pt.lang_id = $1 THEN pt.title END) AS title,
MAX(CASE WHEN pt.lang_id = $1 THEN pt.description END) AS description,
MAX(CASE WHEN pt.lang_id = $1 THEN pt.short_description END) AS short_description
FROM public.project p
JOIN public.project_text pt ON p.id = pt.project_id
GROUP BY p.id, p.date_done, p.picture_name, p.type_project, p.github_link, p.report_link, p.archive_link, p.app_link
ORDER BY p.date_done DESC;",
lang_id
)
.fetch_all(&pool)
.await.unwrap_or(vec![]);
.await.unwrap();
Json(datas)
}
async fn hard_skills(
State(pool): State<PgPool>,
) -> Json<(Vec<ProgrammingLanguages>, Vec<Softwares>, Vec<Languages>)> {
let programming_languages = sqlx::query_as!(
ProgrammingLanguages,
"SELECT lang, icon, type_icon, color FROM public.programming_languages WHERE programming_languages.info_id = $1 ORDER BY programming_languages.id",
id
"SELECT
pl.lang,
pl.icon,
pl.type_icon,
pl.color
FROM public.programming_languages pl
ORDER BY pl.id;"
)
.fetch_all(&pool)
.await.unwrap_or(vec![]);
.await
.unwrap_or(vec![]);
let softwares = sqlx::query_as!(
Softwares,
"SELECT software, icon, type_icon, color FROM public.softwares WHERE softwares.info_id = $1 ORDER BY softwares.id",
id
"SELECT
s.software,
s.icon,
s.type_icon,
s.color
FROM public.softwares s
ORDER BY s.id;"
)
.fetch_all(&pool)
.await.unwrap_or(vec![]);
.await
.unwrap_or(vec![]);
let languages = sqlx::query_as!(
Languages,
"SELECT lang, icon_alpha, level FROM public.languages WHERE languages.info_id = $1 ORDER BY languages.id",
id
"SELECT
l.lang,
l.icon_alpha,
l.level
FROM public.languages l
ORDER BY l.id;"
)
.fetch_all(&pool)
.await.unwrap_or(vec![]);
Json((project, programming_languages, softwares, languages))
.await
.unwrap();
Json((programming_languages, softwares, languages))
}
async fn tags(Path(ids): Path<(i32, i32)>, State(pool): State<PgPool>) -> Json<Vec<Tags>> {
let (info_id, project_id) = ids;
async fn tags(Path(project_id): Path<i32>, State(pool): State<PgPool>) -> Json<Vec<Tags>> {
let datas = sqlx::query_as!(
Tags,
"SELECT lang, icon, type_icon, color
FROM public.programming_languages pl
JOIN public.project_tags pt ON pl.id = pt.programming_languages_id
WHERE pt.info_id = $1 and pt.project_id = $2
WHERE pt.project_id = $1
UNION ALL
SELECT software, icon, type_icon, color
FROM public.softwares s
JOIN public.project_tags pt ON s.id = pt.softwares_id
WHERE pt.info_id = $1 and pt.project_id = $2;
",
info_id,
WHERE pt.project_id = $1;",
project_id
)
.fetch_all(&pool)
.await
.unwrap_or(vec![]);
.unwrap();
Json(datas)
}
async fn alltags(Path(info_id): Path<i32>, State(pool): State<PgPool>) -> Json<Vec<AllTags>> {
async fn alltags(State(pool): State<PgPool>) -> Json<Vec<AllTags>> {
let datas = sqlx::query_as!(
AllTags,
"SELECT project_id, lang, icon, type_icon, color
FROM public.programming_languages pl
JOIN public.project_tags pt ON pl.id = pt.programming_languages_id
WHERE pt.info_id = $1
UNION ALL
SELECT project_id, software, icon, type_icon, color
FROM public.softwares s
JOIN public.project_tags pt ON s.id = pt.softwares_id
WHERE pt.info_id = $1
ORDER BY project_id;
",
info_id
ORDER BY project_id;"
)
.fetch_all(&pool)
.await
.unwrap_or(vec![]);
.unwrap();
Json(datas)
}
async fn getproject_programming(
Path(programming_id): Path<i32>,
Path(ids): Path<(i32,i32)>,
State(pool): State<PgPool>,
) -> Json<Vec<SimpleProject>> {
let (programming_id, lang_id) = ids;
let datas = sqlx::query_as!(
SimpleProject,
"SELECT project_id, title
FROM public.project p
JOIN public.project_tags pt ON p.id = pt.project_id
WHERE pt.programming_languages_id = $1
",
" SELECT
pt.project_id,
pt.title
FROM public.project_text pt
JOIN public.project_tags pta ON (pt.project_id = pta.project_id) AND (pt.lang_id = $1)
WHERE pta.programming_languages_id = $2
ORDER BY pt.project_id;",
lang_id,
programming_id
)
.fetch_all(&pool)
.await
.unwrap_or(vec![]);
.unwrap();
Json(datas)
}
async fn getproject_software(
Path(software_id): Path<i32>,
Path(ids): Path<(i32,i32)>,
State(pool): State<PgPool>,
) -> Json<Vec<SimpleProject>> {
let (software_id, lang_id) = ids;
let datas = sqlx::query_as!(
SimpleProject,
"SELECT project_id, title
FROM public.project p
JOIN public.project_tags pt ON p.id = pt.project_id
WHERE pt.softwares_id = $1
",
" SELECT
pt.project_id,
pt.title
FROM public.project_text pt
JOIN public.project_tags pta ON (pt.project_id = pta.project_id) AND (pt.lang_id = $1)
WHERE pta.softwares_id = $2
ORDER BY pt.project_id;",
lang_id,
software_id
)
.fetch_all(&pool)
.await
.unwrap_or(vec![]);
.unwrap();
Json(datas)
}

View file

@ -20,7 +20,10 @@
export let containerCv = null;
export let sidebarContainer;
let sidebar;
const birth_year = formatDate(info.birth_year);
let birth_year;
if (info.birth_year != null) {
birth_year = formatDate(info.birth_year);
}
$: scrollY = 0;
$: innerHeight = 0;
@ -83,21 +86,29 @@
alt={info.full_name}
/>
</div>
<SidebarComponent icon={mdiAccount} description={birth_year} />
<SidebarComponent icon={mdiEmailOutline} description={info.email} />
{#if info.birth_year != null}
<SidebarComponent icon={mdiAccount} description={birth_year} />
{/if}
{#if info.email != null}
<SidebarComponent icon={mdiEmailOutline} description={info.email} />
{/if}
{#if info.phone_number != null}
<SidebarComponent icon={mdiPhone} description={info.phone_number} />
{/if}
<SidebarComponent
icon={mdiStar}
title="Interests"
description={info.interests}
/>
<SidebarComponent
icon={mdiCogs}
title="Soft-Skills"
description={info.softskills}
/>
{#if info.interests != null}
<SidebarComponent
icon={mdiStar}
title="Interests"
description={info.interests}
/>
{/if}
{#if info.interests != null}
<SidebarComponent
icon={mdiCogs}
title="Soft-Skills"
description={info.softskills}
/>
{/if}
</div>
</div>
{:else}
@ -109,21 +120,29 @@
alt={info.full_name}
/>
</div>
<SidebarComponent icon={mdiAccount} description={birth_year} />
<SidebarComponent icon={mdiEmailOutline} description={info.email} />
{#if info.birth_year != null}
<SidebarComponent icon={mdiAccount} description={birth_year} />
{/if}
{#if info.email != null}
<SidebarComponent icon={mdiEmailOutline} description={info.email} />
{/if}
{#if info.phone_number != null}
<SidebarComponent icon={mdiPhone} description={info.phone_number} />
{/if}
<SidebarComponent
icon={mdiStar}
title="Interests"
description={info.interests}
/>
<SidebarComponent
icon={mdiCogs}
title="Soft-Skills"
description={info.softskills}
/>
{#if info.interests != null}
<SidebarComponent
icon={mdiStar}
title="Interests"
description={info.interests}
/>
{/if}
{#if info.interests != null}
<SidebarComponent
icon={mdiCogs}
title="Soft-Skills"
description={info.softskills}
/>
{/if}
</div>
<div class="fake-sidebar" />
{/if}

View file

@ -9,8 +9,8 @@ function arrangeById(array) {
export function processData(data) {
if (data.status === 0) {
const info = data.info[0];
const experiences = arrangeById(data.experience);
const education = arrangeById(data.education);
const experiences = data.experience;
const education = data.education;
const skills = data.skills;
const tags = data.tags;
const project_programming = data.project_programming;

View file

@ -1,7 +1,6 @@
export function showSidebar(show) {
const sidebarContainer = document.getElementById('sidebar-container');
const body = document.getElementsByTagName('body');
console.log(body[0].style.overflow);
if (show) {
body[0].style.overflow = 'hidden';
@ -10,5 +9,4 @@ export function showSidebar(show) {
body[0].style.overflow = '';
sidebarContainer.style.visibility = 'hidden';
}
console.log(body[0].style.overflow);
}

View file

@ -17,11 +17,15 @@ export async function load(context) {
}
}
// Gathering the language
const lang = context.params.lang;
const lang_id = (await fetchData(`get_lang_id/${lang}`)).data.id;
const infos = [];
const project_software = [];
const project_programming = [];
const dataToGather =
['info', 'education', 'experience', 'skills/1', 'tags/1'];
[`info/${lang_id}`, `education/${lang_id}`, `experience/${lang_id}`, `project/${lang_id}`, 'hard_skills', 'tags'];
for (const url of dataToGather) {
const res = await fetchData(url);
if (res.status == 0) {
@ -34,9 +38,11 @@ export async function load(context) {
}
}
for (let i = 0; i < infos[3][2].length; i++) {
// infos[4] = hardskills
// infos[4][1] = Softwares
for (let i = 0; i < infos[4][1].length; i++) {
const res =
await fetchData(`getproject_software/${i + 1}`);
await fetchData(`getproject_software/${i + 1}/${lang_id}`);
if (res.status == 0) {
project_software.push(res.data);
} else {
@ -45,9 +51,10 @@ export async function load(context) {
}
}
}
for (let i = 0; i < infos[3][1].length; i++) {
// infos[4][0] = Programming Languages
for (let i = 0; i < infos[4][0].length; i++) {
const res =
await fetchData(`getproject_programming/${i + 1}`);
await fetchData(`getproject_programming/${i + 1}/${lang_id}`);
if (res.status == 0) {
project_programming.push(res.data);
}
@ -64,12 +71,12 @@ export async function load(context) {
education: infos[1],
experience: infos[2],
skills: {
project: infos[3][0],
programming_languages: infos[3][1],
softwares: infos[3][2],
languages: infos[3][3],
project: infos[3],
programming_languages: infos[4][0],
softwares: infos[4][1],
languages: infos[4][2],
},
tags: infos[4],
tags: infos[5],
project_programming: project_programming,
project_software: project_software,
};