Compare commits
14 Commits
a767d70354
...
master
Author | SHA1 | Date | |
---|---|---|---|
ec102102c7 | |||
666244e2de | |||
c01751e0a7 | |||
edba0ce60c | |||
0b8c8a7c32 | |||
945c686f15 | |||
7947b2090a | |||
b5699bd7c2 | |||
bc2640dee0 | |||
920ad6ac56 | |||
b23007ee76 | |||
d88ae597a3 | |||
3e6a8e3e37 | |||
75f1e59816 |
@@ -1,5 +1,4 @@
|
||||
target
|
||||
dockerfile
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -18,6 +18,9 @@ node_modules/
|
||||
*.pdb
|
||||
|
||||
*.sqlite
|
||||
*.db
|
||||
|
||||
uploads/
|
||||
database/
|
||||
assets/static/css/main.css
|
||||
.env
|
@@ -2,16 +2,132 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.my-animate {
|
||||
opacity: 1 !important;
|
||||
transform: translateY(0) !important;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
@layer components {
|
||||
.glossy-effect::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 50%;
|
||||
border-radius: var(--radius); /* Inherit parent's border radius */
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.4) 0%,
|
||||
rgba(255, 255, 255, 0.1) 100%
|
||||
);
|
||||
opacity: 0.8;
|
||||
pointer-events: none; /* Allow clicks to pass through */
|
||||
z-index: 1; /* Ensure it's above the background but below content */
|
||||
}
|
||||
|
||||
.hidden-for-animation {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
.glossy-effect.bottom::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 30%;
|
||||
border-radius: var(--radius);
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0.1) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.fa-gloss {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fa-gloss::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 50%;
|
||||
border-radius: var(--radius);
|
||||
background: linear-gradient(var(--gradient-fa-gloss));
|
||||
opacity: 0.8;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.fa-gloss.bottom::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 30%;
|
||||
border-radius: var(--radius);
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0.1) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.shadow-fa-sm {
|
||||
box-shadow: var(--shadow-fa-sm), var(--fa-inner);
|
||||
}
|
||||
.shadow-fa-md {
|
||||
box-shadow: var(--shadow-fa-md), var(--fa-inner);
|
||||
}
|
||||
.shadow-fa-lg {
|
||||
box-shadow: var(--shadow-fa-lg), var(--fa-inner);
|
||||
}
|
||||
.text-shadow-default {
|
||||
text-shadow: var(--text-shadow-default);
|
||||
}
|
||||
.text-shadow-sm {
|
||||
text-shadow: var(--text-shadow-sm);
|
||||
}
|
||||
.text-shadow-md {
|
||||
text-shadow: var(--text-shadow-md);
|
||||
}
|
||||
.text-shadow-lg {
|
||||
text-shadow: var(--text-shadow-lg);
|
||||
}
|
||||
|
||||
.glass-effect {
|
||||
@apply bg-transparent backdrop-blur-lg border border-white/20 shadow-fa-lg;
|
||||
clip-path: inset(0 round 1rem);
|
||||
}
|
||||
|
||||
.gloss-highlight::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60%;
|
||||
border-radius: inherit; /* This is key for matching the parent's border radius */
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.5) 0%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.my-animate {
|
||||
opacity: 1 !important;
|
||||
transform: translateY(0) !important;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.hidden-for-animation {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
BIN
assets/static/images/ja.avif
Normal file
BIN
assets/static/images/ja.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 34 KiB |
Binary file not shown.
Before Width: | Height: | Size: 30 KiB |
BIN
assets/static/images/optimized.avif
Normal file
BIN
assets/static/images/optimized.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
Before Width: | Height: | Size: 585 KiB |
Binary file not shown.
Before Width: | Height: | Size: 42 KiB |
38
assets/static/js/data-delete.js
Normal file
38
assets/static/js/data-delete.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const deleteForms = document.querySelectorAll('.delete-form');
|
||||
|
||||
const deleteData = async (form) => {
|
||||
const fileIdInput = form.querySelector('.file-id');
|
||||
const fileId = fileIdInput.value;
|
||||
if (!fileId) {
|
||||
console.warn('No file selected');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/data/id/${fileId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Data deleted successfully');
|
||||
window.location.reload();
|
||||
fileIdInput.value = '';
|
||||
} else {
|
||||
console.error(
|
||||
'Failed to delete data ',
|
||||
response.status,
|
||||
response.statusText
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting data ', error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteForms.forEach((form) => {
|
||||
const deleteButton = form.querySelector('.delete-button');
|
||||
deleteButton.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
deleteData(form);
|
||||
});
|
||||
});
|
@@ -1,6 +1,7 @@
|
||||
const form = document.getElementById('data-upload');
|
||||
const fileInput = document.getElementById('file-input');
|
||||
const protectedInput = document.getElementById('protected-input');
|
||||
const protectedInput = document.getElementById('protected');
|
||||
const uniqueNameInput = document.getElementById('unique_name');
|
||||
|
||||
const uploadData = async () => {
|
||||
if (!fileInput.files.length) {
|
||||
@@ -11,6 +12,7 @@ const uploadData = async () => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', fileInput.files[0]);
|
||||
formData.append('protected', protectedInput.checked ? 'true' : 'false');
|
||||
formData.append('unique_name', uniqueNameInput.checked ? 'true' : 'false');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/data/upload', {
|
||||
|
@@ -2,31 +2,31 @@
|
||||
%} {% block content %}
|
||||
<div class="mt-8"></div>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center gap-2 p-4 bg-white rounded-lg"
|
||||
class="flex flex-col items-center justify-center gap-2 p-4 bg-white rounded-lg glass-effect glossy-effect bottom gloss-highlight shadow-lg"
|
||||
>
|
||||
<img
|
||||
src="/static/images/ja.jpg"
|
||||
src="/static/images/ja.avif"
|
||||
alt="me"
|
||||
width="300"
|
||||
height="300"
|
||||
class="object-cover rounded-md"
|
||||
class="object-cover rounded-md glossy-effect bottom gloss-highlight shadow-lg"
|
||||
/>
|
||||
<h2 class="mt-4 text-2xl font-bold text-black">Hello, I'm Gabriel! 👋</h2>
|
||||
</div>
|
||||
<h1 class="mt-6 text-3xl font-extrabold">More info about me! 💡</h1>
|
||||
<div
|
||||
class="flex flex-col m-4 prose text-white md:text-left md:m-0 md:w-1/2 md:prose-lg lg:prose-xl prose-blue"
|
||||
class="flex flex-col m-4 prose text-white md:text-left md:m-0 md:w-1/2 md:prose-lg lg:prose-xl prose-yellow"
|
||||
>
|
||||
<p>
|
||||
Hi! I am Gabriel and I am {{ age }} years old. I study Bioinformatics at the
|
||||
University of Gdansk 🏫. I'm fluent in Polish and English and currently work
|
||||
as a Python Developer at digimonkeys.com 🐒💻.
|
||||
Hi! I am Gabriel and I am {{ age }} years old. I graduated in Bioinformatics
|
||||
from the University of Gdańsk 🏫. I'm fluent in Polish and English and
|
||||
currently work as a Python Developer at digimonkeys.com 🐒💻.
|
||||
</p>
|
||||
<p>
|
||||
I have published one article you can read it
|
||||
<a target="_blank" href="http://dx.doi.org/10.1038/s41598-023-44488-7">
|
||||
here
|
||||
</a>.
|
||||
here </a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
<h1 class="mt-6 text-3xl font-extrabold">Hobbies 🎮🎸</h1>
|
||||
@@ -44,6 +44,114 @@
|
||||
Books📚")}} {{ chip::chip(text="Astronomy 🔭")}} {{ chip::chip(text="Sports
|
||||
🏅") }} {{ chip::chip(text="History 🏰")}}
|
||||
</div>
|
||||
|
||||
<h1 class="mt-6 text-3xl font-extrabold">My Philosophy 🧠</h1>
|
||||
<div
|
||||
class="flex flex-col m-4 prose text-white md:text-left md:m-0 md:w-1/2 md:prose-lg lg:prose-xl"
|
||||
>
|
||||
<p>
|
||||
I believe much of today's software is bloated, inefficient, and
|
||||
disrespectful of the user's resources. My passion, which started with a
|
||||
simple curiosity at age 11, is to build a better alternative. I focus on
|
||||
creating software that is
|
||||
<strong class="text-yellow-400"
|
||||
>fast, reliable, and genuinely intuitive</strong
|
||||
>, guided by the principles of clean and efficient code.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h1 class="mt-6 text-3xl font-extrabold">My Toolkit 🛠️</h1>
|
||||
<div
|
||||
class="bg-transparent glass-effect glossy-effect bottom gloss-highlight rounded-2xl p-4 m-4 md:m-0 md:w-1/2 w-full text-white text-shadow-sm"
|
||||
>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold">OS & Hardware</h3>
|
||||
<p>Arch Linux</p>
|
||||
<p>Custom-built PC Rig</p>
|
||||
<p>AMD Ryzen 7 5800X3D</p>
|
||||
<p>NVIDIA RTX 4070 Ti</p>
|
||||
<p>48GB RAM</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold">Editor</h3>
|
||||
<p>Jetbrains IDEs (Pycharm, Rider)</p>
|
||||
<p>VS Code</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold">Primary Languages</h3>
|
||||
<p>Rust, Python, C#, TypeScript</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-bold">Favorite Tech</h3>
|
||||
<p>Axum, Godot, React, TailwindCSS</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="mt-6 text-3xl font-extrabold">Future Goals 🚀</h1>
|
||||
<div
|
||||
class="flex flex-col m-4 prose text-white md:text-left md:m-0 md:w-1/2 md:prose-lg lg:prose-xl prose-blue"
|
||||
>
|
||||
<p>
|
||||
I'm always eager to learn and grow. My goal is to continue honing my skills
|
||||
in backend development and system architecture while exploring new creative
|
||||
outlets. Here's what's on my radar:
|
||||
</p>
|
||||
<ul class="list-none p-0">
|
||||
<li class="flex items-center gap-2 not-prose">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
<span
|
||||
>Deepen my expertise in Rust for high-performance applications.</span
|
||||
>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 not-prose">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
<span>Contribute to impactful open-source projects.</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 not-prose">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
<span>Develop and release my first full-fledged indie game.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h1 class="mt-6 text-3xl font-bold">FAQ ❓</h1>
|
||||
<div id="faq" class="flex flex-col gap-2 m-4">
|
||||
<div>
|
||||
@@ -58,7 +166,9 @@
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-xl font-bold">Are you studying Computer Science?</h6>
|
||||
<p>No, I study Bioinformatics, but it's closely related 🧬💻.</p>
|
||||
<p>
|
||||
No, I have a degree in Bioinformatics, which is a closely related field.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-xl font-bold">
|
||||
|
@@ -7,6 +7,8 @@
|
||||
<title>Gabriel Kaszewski</title>
|
||||
<link rel="icon" href="/static/images/favicon.webp" type="image/x-icon" />
|
||||
<link rel="stylesheet" href="/static/css/main.css" />
|
||||
<script defer data-domain="gabrielkaszewski.dev"
|
||||
src="https://plausible.gabrielkaszewski.dev/js/script.file-downloads.outbound-links.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-800 scroll-smooth">
|
||||
|
@@ -3,21 +3,65 @@
|
||||
<h1 class="text-xl">Gabriel Kaszewski</h1>
|
||||
<span class="flex-1"></span>
|
||||
<a title="github" href="https://github.com/GKaszewski">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-github">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-github"
|
||||
>
|
||||
<path
|
||||
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"
|
||||
/>
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
</a>
|
||||
<a title="linkedin" href="https://www.linkedin.com/in/gabriel-kaszewski-5344b3183">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-linkedin">
|
||||
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" />
|
||||
<a
|
||||
title="linkedin"
|
||||
href="https://www.linkedin.com/in/gabriel-kaszewski-5344b3183"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-linkedin"
|
||||
>
|
||||
<path
|
||||
d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"
|
||||
/>
|
||||
<rect width="4" height="12" x="2" y="9" />
|
||||
<circle cx="4" cy="4" r="2" />
|
||||
</svg>
|
||||
</a>
|
||||
<a title="itch.io" href="https://gabrielkaszewski.itch.io/">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-gamepad"
|
||||
>
|
||||
<rect width="20" height="12" x="2" y="6" rx="2" />
|
||||
<path d="M6 12h4m-2-2v4" />
|
||||
<path d="M16 10h.01M20 10h.01" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex gap-2 text-sm">
|
||||
<p>Powered by loco.rs & Rust 🦀</p>
|
||||
@@ -26,9 +70,7 @@
|
||||
<p class="font-semibold">
|
||||
© Gabriel Kaszewski, 2024. All rights reserved.
|
||||
</p>
|
||||
<p>
|
||||
Made with 💗 in Poland
|
||||
</p>
|
||||
<p>Made with 💗 in Poland</p>
|
||||
<span class="flex-1"></span>
|
||||
<a href="/projects">Projects</a>
|
||||
<a href="https://blog.gabrielkaszewski.dev/" target="_blank">Blog</a>
|
||||
|
18
assets/views/website/create-job.html
Normal file
18
assets/views/website/create-job.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends "website/base.html" %} {% block content %}
|
||||
<div class="w-full mt-16"></div>
|
||||
<form method="post" class="flex flex-col gap-2 text-black" action="/api/jobs">
|
||||
<label for="position">Position</label>
|
||||
<input type="text" name="position" id="position" class="p-2 border border-gray-300 rounded-md" required>
|
||||
<label for="company">Company</label>
|
||||
<input type="text" name="company" id="company" class="p-2 border border-gray-300 rounded-md" required>
|
||||
<label for="technologies">Technologies</label>
|
||||
<input type="text" name="technologies" id="technologies" class="p-2 border border-gray-300 rounded-md" required>
|
||||
<label for="start_date">Start date</label>
|
||||
<input type="date" name="start_date" id="start_date" class="p-2 border border-gray-300 rounded-md" required>
|
||||
<label for="end_date">End date</label>
|
||||
<input type="date" name="end_date" id="end_date" class="p-2 border border-gray-300 rounded-md">
|
||||
<label for="still_working">Still working</label>
|
||||
<input type="checkbox" name="still_working" id="still_working" value="true">
|
||||
<button type="submit" class="p-2 text-gray-900 bg-yellow-500 rounded-sm shadow hover:bg-yellow-600">Submit</button>
|
||||
</form>
|
||||
{% endblock content%}
|
@@ -6,7 +6,11 @@
|
||||
<input id="file-input" type="file" id="file" name="file" required />
|
||||
<div>
|
||||
<label class="text-white" for="protected">Protected:</label>
|
||||
<input id="protected-input" type="checkbox" id="protected" name="protected" />
|
||||
<input type="checkbox" id="protected" name="protected" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-white" for="unique_name">Unique name:</label>
|
||||
<input type="checkbox" id="unique_name" name="unique_name" />
|
||||
</div>
|
||||
<button class="p-2 text-gray-900 bg-yellow-500 rounded-sm shadow hover:bg-yellow-600" type="submit">Upload</button>
|
||||
</form>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
{% extends "website/base.html" %} {% block content %}
|
||||
<script src="/static/js/data-delete.js" defer></script>
|
||||
<span class="mt-8"></span>
|
||||
<a class="p-2 text-gray-900 bg-yellow-500 rounded-sm shadow hover:bg-yellow-600" href="/upload">Add new file</a>
|
||||
<table class="table-fixed">
|
||||
@@ -18,6 +19,12 @@
|
||||
<td>{{ file.protected }}</td>
|
||||
<td><a class="p-2 text-gray-900 bg-yellow-500 rounded-sm shadow hover:bg-yellow-600"
|
||||
href="/api/data/{{ file.file_name }}">Download</a></td>
|
||||
<td>
|
||||
<form class="delete-form">
|
||||
<input class="file-id" id="file_id" type="hidden" name="file_id" value="{{ file.id }}" />
|
||||
<button class="p-2 text-gray-900 bg-red-500 rounded-sm shadow hover:bg-red-600 delete-button">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@@ -3,57 +3,113 @@
|
||||
<div class="w-full">
|
||||
<div class="w-full mt-16 md:hidden"></div>
|
||||
<div class="relative inline-block w-full min-w-full">
|
||||
<img src="/static/images/optimized-75.webp" alt="Background"
|
||||
class="hidden object-cover w-full max-h-full pointer-events-none md:block" />
|
||||
<img
|
||||
src="/static/images/optimized.avif"
|
||||
alt="Background"
|
||||
class="hidden object-cover w-full max-h-full pointer-events-none md:block"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-col items-center justify-start w-full md:inset-0 md:absolute md:items-start md:p-16 lg:p-20">
|
||||
class="flex flex-col items-center justify-start w-full md:inset-0 md:absolute md:items-start md:p-16 lg:p-20"
|
||||
>
|
||||
<div class="hidden md:block">
|
||||
<h1
|
||||
class="mb-4 text-2xl font-bold tracking-tight text-white md:text-4xl lg:text-6xl md:mb-0 -motion-translate-x-in-100 motion-translate-y-in-75">
|
||||
class="mb-4 text-2xl font-bold tracking-tight text-white md:text-4xl lg:text-6xl md:mb-0 -motion-translate-x-in-100 motion-translate-y-in-75"
|
||||
>
|
||||
Gabriel Kaszewski
|
||||
</h1>
|
||||
<h2
|
||||
class="mt-8 text-lg font-light tracking-tight text-white md:text-xl lg:text-2xl md:mt-0 motion-preset-slide-right motion-duration-1000">
|
||||
class="mt-8 text-lg font-light tracking-tight text-white md:text-xl lg:text-2xl md:mt-0 motion-preset-slide-right motion-duration-1000"
|
||||
>
|
||||
Full-Stack Developer
|
||||
</h2>
|
||||
</div>
|
||||
<div class="md:hidden">
|
||||
<h1 class="text-2xl font-bold tracking-tight text-white motion-preset-slide-right motion-duration-1000">
|
||||
<h1
|
||||
class="text-2xl font-bold tracking-tight text-white motion-preset-slide-right motion-duration-1000"
|
||||
>
|
||||
Gabriel Kaszewski
|
||||
</h1>
|
||||
<h2 class="text-lg font-light tracking-tight text-white motion-preset-slide-right motion-duration-1000">
|
||||
<h2
|
||||
class="text-lg font-light tracking-tight text-white motion-preset-slide-right motion-duration-1000"
|
||||
>
|
||||
Full-Stack Developer
|
||||
</h2>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mt-4 motion-preset-slide-right motion-duration-2000 md:mt-0">
|
||||
<div
|
||||
class="flex items-center gap-2 mt-4 motion-preset-slide-right motion-duration-2000 md:mt-0"
|
||||
>
|
||||
<a href="/api/data/cv.pdf" title="My CV">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
||||
<path d="M15 18a3 3 0 1 0-6 0" />
|
||||
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" />
|
||||
<path
|
||||
d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"
|
||||
/>
|
||||
<circle cx="12" cy="13" r="2" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://github.com/GKaszewski" title="GitHub">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"
|
||||
/>
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="mailto: gabrielkaszewski@gmail.com" title="My email">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/in/gabriel-kaszewski-5344b3183" title="LinkedIn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" />
|
||||
<a
|
||||
href="https://www.linkedin.com/in/gabriel-kaszewski-5344b3183"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"
|
||||
/>
|
||||
<rect width="4" height="12" x="2" y="9" />
|
||||
<circle cx="4" cy="4" r="2" />
|
||||
</svg>
|
||||
@@ -63,62 +119,112 @@
|
||||
<div class="absolute bottom-0 hidden p-2 text-sm md:block">
|
||||
<span class="flex gap-1">
|
||||
Photo by
|
||||
<a class="underline"
|
||||
href="https://unsplash.com/@federize?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">
|
||||
<a
|
||||
class="underline"
|
||||
href="https://unsplash.com/@federize?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash"
|
||||
>
|
||||
Federico Beccari
|
||||
</a>
|
||||
on
|
||||
<a class="underline"
|
||||
href="https://unsplash.com/photos/red-moon-eGJg5iRGlg8?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">
|
||||
<a
|
||||
class="underline"
|
||||
href="https://unsplash.com/photos/red-moon-eGJg5iRGlg8?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash"
|
||||
>
|
||||
Unsplash
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="who-am-i" class="flex flex-col items-center justify-center gap-4 p-4 rounded md:w-fit">
|
||||
<div
|
||||
id="who-am-i"
|
||||
class="flex flex-col items-center justify-center gap-4 p-4 rounded md:w-fit"
|
||||
>
|
||||
<h3 class="mt-4 mb-2 text-5xl font-bold tracking-tight">Who am I? 🤔</h3>
|
||||
<section class="prose text-white md:prose-lg lg:prose-xl">
|
||||
<p class="motion-preset-pop motion-delay-75">
|
||||
Hi, my name is Gabriel Kaszewski, and I am a Bioinformatics student 🧬 and
|
||||
self-taught full-stack developer 💻.
|
||||
Hi, my name is Gabriel Kaszewski - I'm a Bioinformatics graduate 🧬 and a
|
||||
self-taught full-stack developer 💻. I love creating software that
|
||||
resolves complex problems, and my most ambitious project yet is my first
|
||||
game. Take a look at the Steam preview below and consider adding it to
|
||||
your wishlist!
|
||||
</p>
|
||||
|
||||
<div class="w-full max-w-full aspect-[646/190]">
|
||||
<iframe
|
||||
class="w-full h-full"
|
||||
src="https://store.steampowered.com/widget/3575090/"
|
||||
frameborder="0"
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
<p class="motion-preset-pop motion-delay-100">
|
||||
My journey with programming started when I was 11 🚀. I love solving problems and creating software that
|
||||
resolves them 👨💻.
|
||||
My journey with programming started when I was 11 🚀. I love solving
|
||||
problems and creating software that resolves them 👨💻.
|
||||
</p>
|
||||
<p class="motion-preset-pop motion-delay-200">
|
||||
Currently, I am working as a Python Developer at digimonkeys.com 🐒. In my free time I like to read about
|
||||
new technologies and work on my projects 📚.
|
||||
Currently, I am working as a Python Developer at digimonkeys.com 🐒. In my
|
||||
free time I like to read about new technologies and work on my projects
|
||||
📚.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
<div id="skills" class="flex flex-col items-center justify-center gap-4 p-4 rounded md:w-fit">
|
||||
<div
|
||||
id="skills"
|
||||
class="flex flex-col items-center justify-center gap-4 p-4 rounded md:w-fit"
|
||||
>
|
||||
<h3 class="mt-4 mb-2 text-5xl font-bold tracking-tight">Skills 🛠️</h3>
|
||||
<div class="flex flex-wrap justify-center w-1/2 gap-4">
|
||||
{% for skill in skills %} <div
|
||||
class="odd:motion-preset-slide-left even:motion-preset-slide-right odd:motion-delay-100">
|
||||
{% for skill in skills %}
|
||||
<div
|
||||
class="odd:motion-preset-slide-left even:motion-preset-slide-right odd:motion-delay-100"
|
||||
>
|
||||
{{ chip::chip(text=skill.name) }}
|
||||
</div>
|
||||
{%endfor%}
|
||||
</div>
|
||||
</div>
|
||||
<div id="experience" class="flex flex-col items-center justify-center gap-4 p-4 rounded md:w-fit">
|
||||
<div
|
||||
id="experience"
|
||||
class="flex flex-col items-center justify-center gap-4 p-4 rounded md:w-fit"
|
||||
>
|
||||
<h3 class="mt-4 mb-2 text-5xl font-bold tracking-tight">Experience 📈</h3>
|
||||
{% for job in jobs %}
|
||||
<div
|
||||
class="flex flex-col gap-2 p-4 text-black bg-gray-50 rounded-lg w-[20rem] max-w-[20rem] shadow-lg odd:motion-preset-slide-right-md odd:motion-delay-100 even:motion-preset-rebound">
|
||||
<h4 class="flex items-center gap-1 text-2xl">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-user-round">
|
||||
class="bg-white glass-effect glossy-effect bottom gloss-highlight flex flex-col gap-2 p-4 text-black w-[20rem] max-w-[20rem] odd:motion-preset-slide-right-md odd:motion-delay-100 even:motion-preset-rebound"
|
||||
>
|
||||
<h4 class="flex items-center gap-1 text-2xl text-shadow-sm">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-circle-user-round"
|
||||
>
|
||||
<path d="M18 20a6 6 0 0 0-12 0" />
|
||||
<circle cx="12" cy="10" r="4" />
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
</svg> {{ job.position }}
|
||||
</svg>
|
||||
{{ job.position }}
|
||||
</h4>
|
||||
<h5 class="flex items-center gap-1 text-xl font-light">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-building">
|
||||
<h5 class="flex items-center gap-1 text-xl font-light text-shadow-sm">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-building"
|
||||
>
|
||||
<rect width="16" height="20" x="4" y="2" rx="2" ry="2" />
|
||||
<path d="M9 22v-4h6v4" />
|
||||
<path d="M8 6h.01" />
|
||||
@@ -130,30 +236,62 @@
|
||||
<path d="M16 14h.01" />
|
||||
<path d="M8 10h.01" />
|
||||
<path d="M8 14h.01" />
|
||||
</svg> {{ job.company }}
|
||||
</svg>
|
||||
{{ job.company }}
|
||||
</h5>
|
||||
{% if job.still_working %}
|
||||
<h6 class="flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-clock"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<polyline points="12 6 12 12 16 14" />
|
||||
</svg> {{ job.start_date |
|
||||
date(format="%d-%m-%Y") }} - Present
|
||||
</svg>
|
||||
{{ job.start_date | date(format="%d-%m-%Y") }} - Present
|
||||
</h6>
|
||||
{% else %}
|
||||
<h6 class="flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-clock"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<polyline points="12 6 12 12 16 14" />
|
||||
</svg> {{ job.start_date |
|
||||
date(format="%d-%m-%Y") }} - {{ job.end_date | date(format="%d-%m-%Y") }}
|
||||
</svg>
|
||||
{{ job.start_date | date(format="%d-%m-%Y") }} - {{ job.end_date |
|
||||
date(format="%d-%m-%Y") }}
|
||||
</h6>
|
||||
{% endif %}
|
||||
<p class="flex items-center gap-1 font-bold"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="lucide lucide-microchip">
|
||||
<p class="flex items-center gap-1 font-bold">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-microchip"
|
||||
>
|
||||
<path d="M18 12h2" />
|
||||
<path d="M18 16h2" />
|
||||
<path d="M18 20h2" />
|
||||
@@ -165,11 +303,14 @@
|
||||
<path d="M4 4h2" />
|
||||
<path d="M4 8h2" />
|
||||
<path
|
||||
d="M8 2a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-1.5c-.276 0-.494.227-.562.495a2 2 0 0 1-3.876 0C9.994 2.227 9.776 2 9.5 2z" />
|
||||
</svg> Technologies</p>
|
||||
d="M8 2a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-1.5c-.276 0-.494.227-.562.495a2 2 0 0 1-3.876 0C9.994 2.227 9.776 2 9.5 2z"
|
||||
/>
|
||||
</svg>
|
||||
Technologies
|
||||
</p>
|
||||
<div class="flex flex-wrap items-center w-full gap-2">
|
||||
{% for technology in job.technologies %} {{ chip::chip(text=technology)
|
||||
}} {% endfor %}
|
||||
{% for technology in job.technologies %} {{ chip::chip(text=technology) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{%endfor%}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{% macro chip(text) %}
|
||||
<p
|
||||
class="text-base text-center rounded-2xl font-semibold tracking-tight text-black shadow-lg bg-yellow-400 p-2 min-w-[4rem] max-w-[12rem]"
|
||||
class="text-base text-center rounded-2xl font-semibold tracking-tight text-black bg-yellow-400 p-2 min-w-[4rem] max-w-[12rem] glass-effect glossy-effect bottom gloss-highlight"
|
||||
>
|
||||
{{ text }}
|
||||
</p>
|
||||
|
@@ -1,202 +1,343 @@
|
||||
{% import "website/macros/chip.html" as chip %}
|
||||
{% import "website/macros/image-carousel.html" as image_carousel %}
|
||||
{% macro project_item(project) %}
|
||||
{% import "website/macros/chip.html" as chip %} {% import
|
||||
"website/macros/image-carousel.html" as image_carousel %} {% macro
|
||||
project_item(project) %}
|
||||
<script src="/static/js/project-item.js"></script>
|
||||
|
||||
<div class="flex items-center justify-between w-full h-full gap-4 text-white">
|
||||
<div class="flex flex-col w-full gap-4 m-4 md:w-1/3">
|
||||
<div class="prose">
|
||||
<h1 class="text-white">{{ project.name }}</h1>
|
||||
<p class="text-white whitespace-pre-wrap">{{ project.short_description }}</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center gap-2 md:justify-start">
|
||||
{% for technology in project.technologies %}
|
||||
{{ chip::chip(text=technology) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a href="/projects/project/{{ project.name }}"
|
||||
class="w-full p-2 text-center border border-yellow-400 rounded-xl hover:bg-yellow-400">Read more</a>
|
||||
<div class="flex flex-wrap gap-2 sm:justify-center md:justify-start">
|
||||
{% if project.github_url %}
|
||||
<a href="{{ project.github_url }}" target="_blank" rel="noopener noreferrer"
|
||||
class="flex items-center justify-center w-full gap-1 p-2 text-center border border-yellow-400 jus rounded-xl hover:bg-yellow-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-github">
|
||||
<path
|
||||
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
CODE
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if project.visit_url %}
|
||||
<a href="{{ project.visit_url }}" target="_blank" rel="noopener noreferrer"
|
||||
class="flex items-center justify-center w-full gap-1 p-2 text-center border border-yellow-400 jus rounded-xl hover:bg-yellow-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-eye">
|
||||
<path
|
||||
d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
LIVE
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if project.download_url %}
|
||||
<a href="{{ project.download_url }}" target="_blank" rel="noopener noreferrer"
|
||||
class="flex items-center justify-center w-full gap-1 p-2 text-center border border-yellow-400 jus rounded-xl hover:bg-yellow-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-cloud-download">
|
||||
<path d="M12 13v8l-4-4" />
|
||||
<path d="m12 21 4-4" />
|
||||
<path d="M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284" />
|
||||
</svg>
|
||||
DOWNLOAD
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if project.thumbnails|length > 0 %}
|
||||
<div class="w-full m-2 md:hidden">
|
||||
{% set carousel_id = "carousel-mobile-" ~ project.id %}
|
||||
{{ image_carousel::image_carousel(id=carousel_id, thumbnails=project.thumbnails) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if project.thumbnails|length == 0 %}
|
||||
<div class="w-full m-2 md:hidden">
|
||||
<div
|
||||
class="bg-gradient-to-r from-violet-600 to-indigo-600 shadow-lg w-full max-w-full md:max-w-[50hw] h-[40rem] flex items-center justify-center">
|
||||
{% if project.category == "Desktop" %}
|
||||
<svg title="desktop" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-app-window">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
<path d="M10 4v4" />
|
||||
<path d="M2 8h20" />
|
||||
<path d="M6 4v4" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
{% if project.category == "Mobile" %}
|
||||
<svg title="mobile" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-smartphone">
|
||||
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
|
||||
<path d="M12 18h.01" />
|
||||
</svg>
|
||||
{%endif%}
|
||||
{% if project.category == "Web" %}
|
||||
<svg title="web" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-globe">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||
<path d="M2 12h20" />
|
||||
</svg>
|
||||
{%endif%}
|
||||
{% if project.category == "Api" %}
|
||||
<svg title="API" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-monitor-cog">
|
||||
<path d="M12 17v4" />
|
||||
<path d="m15.2 4.9-.9-.4" />
|
||||
<path d="m15.2 7.1-.9.4" />
|
||||
<path d="m16.9 3.2-.4-.9" />
|
||||
<path d="m16.9 8.8-.4.9" />
|
||||
<path d="m19.5 2.3-.4.9" />
|
||||
<path d="m19.5 9.7-.4-.9" />
|
||||
<path d="m21.7 4.5-.9.4" />
|
||||
<path d="m21.7 7.5-.9-.4" />
|
||||
<path d="M22 13v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7" />
|
||||
<path d="M8 21h8" />
|
||||
<circle cx="18" cy="6" r="3" />
|
||||
</svg>
|
||||
{%endif%}
|
||||
{% if project.category == "Game" %}
|
||||
<svg title="game" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-gamepad-2">
|
||||
<line x1="6" x2="10" y1="11" y2="11" />
|
||||
<line x1="8" x2="8" y1="9" y2="13" />
|
||||
<line x1="15" x2="15.01" y1="12" y2="12" />
|
||||
<line x1="18" x2="18.01" y1="10" y2="10" />
|
||||
<path
|
||||
d="M17.32 5H6.68a4 4 0 0 0-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 0 0 3 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 0 1 9.828 16h4.344a2 2 0 0 1 1.414.586L17 18c.5.5 1 1 2 1a3 3 0 0 0 3-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0 0 17.32 5z" />
|
||||
</svg>
|
||||
{%endif%}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="flex flex-col w-full gap-4 m-4 md:w-1/3">
|
||||
<div class="prose">
|
||||
<h1 class="text-white">{{ project.name }}</h1>
|
||||
<p class="text-white whitespace-pre-wrap">
|
||||
{{ project.short_description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center gap-2 md:justify-start">
|
||||
{% for technology in project.technologies %} {{
|
||||
chip::chip(text=technology) }} {% endfor %}
|
||||
</div>
|
||||
<a
|
||||
href="/projects/project/{{ project.name }}"
|
||||
class="text-center glass-effect glossy-effect bottom gloss-highlight p-2 rounded-2xl bg-yellow-400 hover:bg-yellow-500 text-black"
|
||||
>Read more</a
|
||||
>
|
||||
<div class="flex flex-wrap gap-2 sm:justify-center md:justify-start">
|
||||
{% if project.github_url %}
|
||||
<a
|
||||
href="{{ project.github_url }}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center justify-center w-full gap-1 p-2 text-center glass-effect glossy-effect bottom gloss-highlight rounded-2xl bg-yellow-400 hover:bg-yellow-500 text-black"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-github"
|
||||
>
|
||||
<path
|
||||
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"
|
||||
/>
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
CODE
|
||||
</a>
|
||||
{% endif %} {% if project.visit_url %}
|
||||
<a
|
||||
href="{{ project.visit_url }}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center justify-center w-full gap-1 p-2 text-center glass-effect glossy-effect bottom gloss-highlight rounded-2xl bg-yellow-400 hover:bg-yellow-500 text-black"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-eye"
|
||||
>
|
||||
<path
|
||||
d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"
|
||||
/>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
LIVE
|
||||
</a>
|
||||
{% endif %} {% if project.download_url %}
|
||||
<a
|
||||
href="{{ project.download_url }}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center justify-center w-full gap-1 p-2 text-center glass-effect glossy-effect bottom gloss-highlight rounded-2xl bg-yellow-400 hover:bg-yellow-500 text-black"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-cloud-download"
|
||||
>
|
||||
<path d="M12 13v8l-4-4" />
|
||||
<path d="m12 21 4-4" />
|
||||
<path
|
||||
d="M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284"
|
||||
/>
|
||||
</svg>
|
||||
DOWNLOAD
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if project.thumbnails|length > 0 %}
|
||||
<div class="hidden m-2 md:flex md:w-1/2">
|
||||
{% set carousel_id = "carousel-desktop-" ~ project.id %}
|
||||
{{ image_carousel::image_carousel(id=carousel_id, thumbnails=project.thumbnails) }}
|
||||
<div class="w-full m-2 md:hidden">
|
||||
{% set carousel_id = "carousel-mobile-" ~ project.id %} {{
|
||||
image_carousel::image_carousel(id=carousel_id,
|
||||
thumbnails=project.thumbnails) }}
|
||||
</div>
|
||||
{% endif %} {% if project.thumbnails|length == 0 %}
|
||||
<div class="w-full m-2 md:hidden">
|
||||
<div
|
||||
class="bg-gradient-to-r from-violet-600 to-indigo-600 shadow-lg w-full max-w-full md:max-w-[50hw] h-[40rem] flex items-center justify-center glass-effect glossy-effect bottom gloss-highlight"
|
||||
>
|
||||
{% if project.category == "Desktop" %}
|
||||
<svg
|
||||
title="desktop"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-app-window"
|
||||
>
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
<path d="M10 4v4" />
|
||||
<path d="M2 8h20" />
|
||||
<path d="M6 4v4" />
|
||||
</svg>
|
||||
{% endif %} {% if project.category == "Mobile" %}
|
||||
<svg
|
||||
title="mobile"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-smartphone"
|
||||
>
|
||||
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
|
||||
<path d="M12 18h.01" />
|
||||
</svg>
|
||||
{%endif%} {% if project.category == "Web" %}
|
||||
<svg
|
||||
title="web"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-globe"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||
<path d="M2 12h20" />
|
||||
</svg>
|
||||
{%endif%} {% if project.category == "Api" %}
|
||||
<svg
|
||||
title="API"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-monitor-cog"
|
||||
>
|
||||
<path d="M12 17v4" />
|
||||
<path d="m15.2 4.9-.9-.4" />
|
||||
<path d="m15.2 7.1-.9.4" />
|
||||
<path d="m16.9 3.2-.4-.9" />
|
||||
<path d="m16.9 8.8-.4.9" />
|
||||
<path d="m19.5 2.3-.4.9" />
|
||||
<path d="m19.5 9.7-.4-.9" />
|
||||
<path d="m21.7 4.5-.9.4" />
|
||||
<path d="m21.7 7.5-.9-.4" />
|
||||
<path d="M22 13v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7" />
|
||||
<path d="M8 21h8" />
|
||||
<circle cx="18" cy="6" r="3" />
|
||||
</svg>
|
||||
{%endif%} {% if project.category == "Game" %}
|
||||
<svg
|
||||
title="game"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-gamepad-2"
|
||||
>
|
||||
<line x1="6" x2="10" y1="11" y2="11" />
|
||||
<line x1="8" x2="8" y1="9" y2="13" />
|
||||
<line x1="15" x2="15.01" y1="12" y2="12" />
|
||||
<line x1="18" x2="18.01" y1="10" y2="10" />
|
||||
<path
|
||||
d="M17.32 5H6.68a4 4 0 0 0-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 0 0 3 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 0 1 9.828 16h4.344a2 2 0 0 1 1.414.586L17 18c.5.5 1 1 2 1a3 3 0 0 0 3-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0 0 17.32 5z"
|
||||
/>
|
||||
</svg>
|
||||
{%endif%}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if project.thumbnails|length == 0 %}
|
||||
<div class="hidden m-2 md:flex md:w-1/2">
|
||||
<div
|
||||
class="bg-gradient-to-r from-violet-600 to-indigo-600 shadow-lg w-full max-w-full md:max-w-[50hw] h-[40rem] flex items-center justify-center">
|
||||
{% if project.category == "Desktop" %}
|
||||
<svg title="desktop" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-app-window">
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
<path d="M10 4v4" />
|
||||
<path d="M2 8h20" />
|
||||
<path d="M6 4v4" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
{% if project.category == "Mobile" %}
|
||||
<svg title="mobile" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-smartphone">
|
||||
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
|
||||
<path d="M12 18h.01" />
|
||||
</svg>
|
||||
{%endif%}
|
||||
{% if project.category == "Web" %}
|
||||
<svg title="web" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-globe">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||
<path d="M2 12h20" />
|
||||
</svg>
|
||||
{%endif%}
|
||||
{% if project.category == "Api" %}
|
||||
<svg title="API" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-monitor-cog">
|
||||
<path d="M12 17v4" />
|
||||
<path d="m15.2 4.9-.9-.4" />
|
||||
<path d="m15.2 7.1-.9.4" />
|
||||
<path d="m16.9 3.2-.4-.9" />
|
||||
<path d="m16.9 8.8-.4.9" />
|
||||
<path d="m19.5 2.3-.4.9" />
|
||||
<path d="m19.5 9.7-.4-.9" />
|
||||
<path d="m21.7 4.5-.9.4" />
|
||||
<path d="m21.7 7.5-.9-.4" />
|
||||
<path d="M22 13v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7" />
|
||||
<path d="M8 21h8" />
|
||||
<circle cx="18" cy="6" r="3" />
|
||||
</svg>
|
||||
{%endif%}
|
||||
{% if project.category == "Game" %}
|
||||
<svg title="game" xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-gamepad-2">
|
||||
<line x1="6" x2="10" y1="11" y2="11" />
|
||||
<line x1="8" x2="8" y1="9" y2="13" />
|
||||
<line x1="15" x2="15.01" y1="12" y2="12" />
|
||||
<line x1="18" x2="18.01" y1="10" y2="10" />
|
||||
<path
|
||||
d="M17.32 5H6.68a4 4 0 0 0-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 0 0 3 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 0 1 9.828 16h4.344a2 2 0 0 1 1.414.586L17 18c.5.5 1 1 2 1a3 3 0 0 0 3-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0 0 17.32 5z" />
|
||||
</svg>
|
||||
{%endif%}
|
||||
</div>
|
||||
</div>
|
||||
{% if project.thumbnails|length > 0 %}
|
||||
<div class="hidden m-2 md:flex md:w-1/2">
|
||||
{% set carousel_id = "carousel-desktop-" ~ project.id %} {{
|
||||
image_carousel::image_carousel(id=carousel_id,
|
||||
thumbnails=project.thumbnails) }}
|
||||
</div>
|
||||
{% endif %} {% if project.thumbnails|length == 0 %}
|
||||
<div class="hidden m-2 md:flex md:w-1/2">
|
||||
<div
|
||||
class="bg-gradient-to-r from-violet-600 to-indigo-600 shadow-lg w-full max-w-full md:max-w-[50hw] h-[40rem] flex items-center justify-center glass-effect glossy-effect bottom gloss-highlight"
|
||||
>
|
||||
{% if project.category == "Desktop" %}
|
||||
<svg
|
||||
title="desktop"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-app-window"
|
||||
>
|
||||
<rect x="2" y="4" width="20" height="16" rx="2" />
|
||||
<path d="M10 4v4" />
|
||||
<path d="M2 8h20" />
|
||||
<path d="M6 4v4" />
|
||||
</svg>
|
||||
{% endif %} {% if project.category == "Mobile" %}
|
||||
<svg
|
||||
title="mobile"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-smartphone"
|
||||
>
|
||||
<rect width="14" height="20" x="5" y="2" rx="2" ry="2" />
|
||||
<path d="M12 18h.01" />
|
||||
</svg>
|
||||
{%endif%} {% if project.category == "Web" %}
|
||||
<svg
|
||||
title="web"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-globe"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
|
||||
<path d="M2 12h20" />
|
||||
</svg>
|
||||
{%endif%} {% if project.category == "Api" %}
|
||||
<svg
|
||||
title="API"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-monitor-cog"
|
||||
>
|
||||
<path d="M12 17v4" />
|
||||
<path d="m15.2 4.9-.9-.4" />
|
||||
<path d="m15.2 7.1-.9.4" />
|
||||
<path d="m16.9 3.2-.4-.9" />
|
||||
<path d="m16.9 8.8-.4.9" />
|
||||
<path d="m19.5 2.3-.4.9" />
|
||||
<path d="m19.5 9.7-.4-.9" />
|
||||
<path d="m21.7 4.5-.9.4" />
|
||||
<path d="m21.7 7.5-.9-.4" />
|
||||
<path d="M22 13v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7" />
|
||||
<path d="M8 21h8" />
|
||||
<circle cx="18" cy="6" r="3" />
|
||||
</svg>
|
||||
{%endif%} {% if project.category == "Game" %}
|
||||
<svg
|
||||
title="game"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-gamepad-2"
|
||||
>
|
||||
<line x1="6" x2="10" y1="11" y2="11" />
|
||||
<line x1="8" x2="8" y1="9" y2="13" />
|
||||
<line x1="15" x2="15.01" y1="12" y2="12" />
|
||||
<line x1="18" x2="18.01" y1="10" y2="10" />
|
||||
<path
|
||||
d="M17.32 5H6.68a4 4 0 0 0-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 0 0 3 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 0 1 9.828 16h4.344a2 2 0 0 1 1.414.586L17 18c.5.5 1 1 2 1a3 3 0 0 0 3-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0 0 17.32 5z"
|
||||
/>
|
||||
</svg>
|
||||
{%endif%}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro project_item %}
|
14
compose.yml
Normal file
14
compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
website:
|
||||
build: .
|
||||
container_name: gabrielkaszewski-website
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:5150"
|
||||
volumes:
|
||||
- ./database:/app/db
|
||||
- ./uploads:/app/uploads
|
||||
environment:
|
||||
- JWT_SECRET=your_super_secret_production_jwt_key_here
|
||||
- HOST=https://your-domain.com
|
||||
- BINDING=0.0.0.0
|
@@ -7,7 +7,7 @@ logger:
|
||||
# Enable pretty backtrace (sets RUST_BACKTRACE=1)
|
||||
pretty_backtrace: true
|
||||
# Log level, options: trace, debug, info, warn or error.
|
||||
level: debug
|
||||
level: {{ get_env(name="LOGGER_LEVEL", default="debug") }}
|
||||
# Define the logging format. options: compact, pretty or json
|
||||
format: compact
|
||||
# By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries
|
||||
@@ -19,6 +19,7 @@ server:
|
||||
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
|
||||
port: 5150
|
||||
# The UI hostname or IP address that mailers will point to.
|
||||
binding: {{ get_env(name="BINDING", default="localhost") }}
|
||||
host: {{ get_env(name="HOST", default="http://localhost") }}
|
||||
# Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block
|
||||
middlewares:
|
||||
@@ -53,25 +54,6 @@ server:
|
||||
path: "assets/static"
|
||||
fallback: "assets/static/404.html"
|
||||
|
||||
#
|
||||
# (2) Client side app static config
|
||||
# =================================
|
||||
#
|
||||
# Note that you need to go in `frontend` and run your frontend build first,
|
||||
# e.g.: $ npm install & npm build
|
||||
#
|
||||
# (client-block-start)
|
||||
# static:
|
||||
# enable: true
|
||||
# must_exist: true
|
||||
# precompressed: false
|
||||
# folder:
|
||||
# uri: "/"
|
||||
# path: "frontend/dist"
|
||||
# fallback: "frontend/dist/index.html"
|
||||
# (client-block-end)
|
||||
#
|
||||
|
||||
# Worker Configuration
|
||||
workers:
|
||||
# specifies the worker mode. Options:
|
||||
@@ -106,7 +88,7 @@ mailer:
|
||||
# Database Configuration
|
||||
database:
|
||||
# Database connection URI
|
||||
uri: {{ get_env(name="DATABASE_URL", default="postgres://postgres:postgres@localhost:5432/gabrielkaszewski_rs") }}
|
||||
uri: {{ get_env(name="DATABASE_URL", default="sqlite://./database.db?mode=rwc") }}
|
||||
# When enabled, the sql query will be logged.
|
||||
enable_logging: false
|
||||
# Set the timeout duration when acquiring a connection.
|
||||
|
53
dockerfile
53
dockerfile
@@ -1,18 +1,51 @@
|
||||
FROM rust:1.74-slim as builder
|
||||
# =================================================================
|
||||
# Stage 1: Build the Rust application
|
||||
# =================================================================
|
||||
FROM rust:1.89-slim-bookworm AS builder
|
||||
|
||||
WORKDIR /usr/src/
|
||||
RUN apt-get update && apt-get install -y libsqlite3-dev pkg-config build-essential
|
||||
|
||||
COPY . .
|
||||
WORKDIR /app
|
||||
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY .cargo ./.cargo/
|
||||
COPY migration ./migration
|
||||
|
||||
RUN mkdir -p src/bin && \
|
||||
echo "fn main() {}" > src/bin/main.rs && \
|
||||
echo "fn main() {}" > src/bin/tool.rs
|
||||
RUN cargo build --release
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
COPY src ./src
|
||||
COPY assets ./assets
|
||||
COPY config ./config
|
||||
RUN cargo build --release
|
||||
|
||||
WORKDIR /usr/app
|
||||
# =================================================================
|
||||
# Stage 2: Create the final, lightweight runtime image
|
||||
# =================================================================
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
|
||||
COPY --from=builder /usr/src/assets/static /usr/app/assets/static
|
||||
COPY --from=builder /usr/src/assets/static/404.html /usr/app/assets/static/404.html
|
||||
COPY --from=builder /usr/src/config /usr/app/config
|
||||
COPY --from=builder /usr/src/target/release/gabrielkaszewski_rs-cli /usr/app/gabrielkaszewski_rs-cli
|
||||
RUN apt-get update && apt-get install -y libsqlite3-0 libssl3 gosu && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENTRYPOINT ["/usr/app/gabrielkaszewski_rs-cli", "start"]
|
||||
RUN addgroup --system nonroot && adduser --system --ingroup nonroot nonroot
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/target/release/gabrielkaszewski_rs-cli ./server
|
||||
|
||||
COPY assets ./assets
|
||||
COPY config ./config
|
||||
|
||||
RUN mkdir -p /app/db /app/uploads && chown -R nonroot:nonroot /app/db /app/uploads
|
||||
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
ENV LOCO_ENV=production
|
||||
ENV DATABASE_URL=sqlite:///app/db/production.db?mode=rwc
|
||||
|
||||
EXPOSE 5150
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
CMD ["./server", "start"]
|
6
entrypoint.sh
Normal file
6
entrypoint.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
chown -R nonroot:nonroot /app/db /app/uploads
|
||||
|
||||
exec gosu nonroot "$@"
|
@@ -13,47 +13,26 @@ enum Projects {
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
//
|
||||
// add column
|
||||
//
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Projects::Table)
|
||||
.add_column_if_not_exists(boolean(Projects::IsHighlighted).default(false))
|
||||
.add_column_if_not_exists(boolean(Projects::IsArchived).default(false))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
.await?;
|
||||
|
||||
//
|
||||
// delete column
|
||||
//
|
||||
/*
|
||||
// Add `is_archived` column
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Movies::Table)
|
||||
.drop_column(Movies::Rating)
|
||||
.table(Projects::Table)
|
||||
.add_column_if_not_exists(boolean(Projects::IsArchived).default(false))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
*/
|
||||
.await?;
|
||||
|
||||
//
|
||||
// create index
|
||||
//
|
||||
/*
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx-movies-rating")
|
||||
.table(Movies::Table)
|
||||
.col(Movies::Rating)
|
||||
.to_owned(),
|
||||
)
|
||||
.await;
|
||||
*/
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
|
@@ -4,12 +4,12 @@
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"tailwindcss-motion": "^0.4.1-beta"
|
||||
"tailwindcss-motion": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.15"
|
||||
"@tailwindcss/typography": "^0.5.16"
|
||||
}
|
||||
}
|
@@ -48,6 +48,7 @@ impl Hooks for App {
|
||||
|
||||
fn routes(_ctx: &AppContext) -> AppRoutes {
|
||||
AppRoutes::with_default_routes() // controller routes below
|
||||
.add_route(controllers::job::routes())
|
||||
.add_route(controllers::project::routes())
|
||||
.add_route(controllers::data::routes())
|
||||
.add_route(controllers::auth::routes())
|
||||
@@ -77,6 +78,7 @@ impl Hooks for App {
|
||||
tasks.register(tasks::create_user::CreateUserData);
|
||||
tasks.register(tasks::create_job::CreateJobData);
|
||||
tasks.register(tasks::create_skill::CreateSkillData);
|
||||
tasks.register(tasks::import_skills::ImportSkills);
|
||||
tasks.register(tasks::add_data_file::AddDataFile);
|
||||
tasks.register(tasks::delete_data::DeleteData);
|
||||
tasks.register(tasks::clear_data::ClearData);
|
||||
|
@@ -14,7 +14,7 @@ use axum_extra::TypedHeader;
|
||||
use crate::models::users;
|
||||
use crate::services;
|
||||
|
||||
pub async fn get_data(
|
||||
async fn get_data(
|
||||
auth: Option<auth::JWT>,
|
||||
range: Option<TypedHeader<Range>>,
|
||||
Path(file_name): Path<String>,
|
||||
@@ -23,7 +23,7 @@ pub async fn get_data(
|
||||
services::data::serve_data_file(&auth, range, &file_name, &ctx).await
|
||||
}
|
||||
|
||||
pub async fn upload_data(
|
||||
async fn upload_data(
|
||||
auth: auth::JWT,
|
||||
State(ctx): State<AppContext>,
|
||||
payload: Multipart,
|
||||
@@ -37,9 +37,24 @@ pub async fn upload_data(
|
||||
format::html("<h1>File uploaded successfully</h1>")
|
||||
}
|
||||
|
||||
async fn delete_data(
|
||||
auth: auth::JWT,
|
||||
State(ctx): State<AppContext>,
|
||||
Path(file_id): Path<i32>,
|
||||
) -> Result<Response> {
|
||||
match users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await {
|
||||
Ok(_) => {}
|
||||
Err(_) => return unauthorized("Unauthorized"),
|
||||
}
|
||||
|
||||
services::data::delete_data_by_id(file_id, &ctx).await?;
|
||||
format::html("<h1>File deleted successfully</h1>")
|
||||
}
|
||||
|
||||
pub fn routes() -> Routes {
|
||||
Routes::new()
|
||||
.prefix("api/data")
|
||||
.add("/upload", post(upload_data))
|
||||
.add("/:file_name", get(get_data))
|
||||
.add("/id/:file_id", delete(delete_data))
|
||||
}
|
||||
|
27
src/controllers/job.rs
Normal file
27
src/controllers/job.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::unnecessary_struct_initialization)]
|
||||
#![allow(clippy::unused_async)]
|
||||
use loco_rs::prelude::*;
|
||||
|
||||
use crate::{
|
||||
models::{jobs::CreateJobForm, users},
|
||||
services,
|
||||
};
|
||||
|
||||
async fn create_job(
|
||||
auth: auth::JWT,
|
||||
State(ctx): State<AppContext>,
|
||||
Form(job_data): Form<CreateJobForm>,
|
||||
) -> Result<Response> {
|
||||
match users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await {
|
||||
Ok(_) => {}
|
||||
Err(_) => return unauthorized("Unauthorized"),
|
||||
}
|
||||
|
||||
let job = services::jobs::create_job_from_form(&ctx, &job_data).await?;
|
||||
format::json(&job)
|
||||
}
|
||||
|
||||
pub fn routes() -> Routes {
|
||||
Routes::new().prefix("api/jobs/").add("/", post(create_job))
|
||||
}
|
@@ -3,3 +3,4 @@ pub mod website;
|
||||
|
||||
pub mod data;
|
||||
pub mod project;
|
||||
pub mod job;
|
@@ -80,6 +80,19 @@ pub async fn render_data(
|
||||
views::data::list(v, &ctx).await
|
||||
}
|
||||
|
||||
async fn render_create_job(
|
||||
auth: auth::JWT,
|
||||
ViewEngine(v): ViewEngine<TeraView>,
|
||||
State(ctx): State<AppContext>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
match users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await {
|
||||
Ok(_) => {}
|
||||
Err(_) => return unauthorized("Unauthorized"),
|
||||
}
|
||||
|
||||
views::job::create_job(v).await
|
||||
}
|
||||
|
||||
pub fn routes() -> Routes {
|
||||
Routes::new()
|
||||
.add("/", get(render_index))
|
||||
@@ -89,6 +102,7 @@ pub fn routes() -> Routes {
|
||||
.add("/projects/create", get(render_create_project))
|
||||
.add("/projects/:id", get(render_project_detail))
|
||||
.add("/projects/project/:name", get(render_project_detail_from_name))
|
||||
.add("/jobs/create", get(render_create_job))
|
||||
.add("/data", get(render_data))
|
||||
.add("/about", get(render_about))
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
use super::_entities::jobs::{ActiveModel, Entity};
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::NaiveDate;
|
||||
|
||||
pub type Jobs = Entity;
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {
|
||||
@@ -17,3 +19,14 @@ pub struct JobWithTechnologies {
|
||||
pub technologies: Vec<String>,
|
||||
pub still_working: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreateJobForm {
|
||||
pub position: String,
|
||||
pub company: String,
|
||||
pub start_date: NaiveDate,
|
||||
#[serde(default)]
|
||||
pub end_date: Option<NaiveDate>,
|
||||
pub technologies: String,
|
||||
pub still_working: bool,
|
||||
}
|
@@ -109,7 +109,8 @@ pub async fn add(
|
||||
let mut protected = None;
|
||||
let mut file_name = None;
|
||||
let mut content = None;
|
||||
let mut file_path = None;
|
||||
let mut unique_name = None;
|
||||
let mut ext = None;
|
||||
|
||||
while let Some(field) = payload
|
||||
.next_field()
|
||||
@@ -128,25 +129,23 @@ pub async fn add(
|
||||
.parse::<bool>()
|
||||
.map_err(|_| ModelError::Any("Failed to parse bool".into()))?;
|
||||
protected = Some(value);
|
||||
}
|
||||
},
|
||||
"unique_name" => {
|
||||
let value = field
|
||||
.text()
|
||||
.await
|
||||
.map_err(|_| ModelError::Any("Failed to get text".into()))?
|
||||
.parse::<bool>()
|
||||
.map_err(|_| ModelError::Any("Failed to parse bool".into()))?;
|
||||
unique_name = Some(value);
|
||||
},
|
||||
"file" => {
|
||||
let (og_file_name, ext) = get_file_name_with_extension_from_field(&field, "txt").map_err(|_| ModelError::Any("Failed to get file name".into()))?;
|
||||
let (og_file_name, extension) = get_file_name_with_extension_from_field(&field, "txt").map_err(|_| ModelError::Any("Failed to get file name".into()))?;
|
||||
|
||||
tracing::info!("File name: {:?}", og_file_name);
|
||||
let og_file_name = format!("{}.{}", og_file_name, extension);
|
||||
ext = Some(extension.clone());
|
||||
|
||||
let temp_file_name = if uuid_name {
|
||||
let temp_file_name = uuid::Uuid::new_v4().to_string();
|
||||
format!("{}.{}", temp_file_name, ext)
|
||||
} else {
|
||||
og_file_name.to_string()
|
||||
};
|
||||
|
||||
tracing::info!("Temp file name: {:?}", temp_file_name);
|
||||
|
||||
file_name = Some(temp_file_name.clone());
|
||||
|
||||
let path = PathBuf::from(temp_file_name);
|
||||
file_path = Some(path.clone());
|
||||
file_name = Some(og_file_name.clone());
|
||||
|
||||
let data_content = field
|
||||
.bytes()
|
||||
@@ -161,9 +160,20 @@ pub async fn add(
|
||||
|
||||
let protected =
|
||||
protected.ok_or_else(|| ModelError::Any("Protected field is required".into()))?;
|
||||
|
||||
let unique_name = unique_name.unwrap_or(true);
|
||||
let file_name = file_name.ok_or_else(|| ModelError::Any("File field is required".into()))?;
|
||||
|
||||
let file_name = match (uuid_name, unique_name) {
|
||||
(true, true) => {
|
||||
let temp_file_name = uuid::Uuid::new_v4().to_string();
|
||||
format!("{}.{}", temp_file_name, ext.unwrap_or("txt".to_string()))
|
||||
}
|
||||
_ => file_name,
|
||||
};
|
||||
|
||||
|
||||
let path = PathBuf::from(file_name.clone());
|
||||
|
||||
let mut item = ActiveModel {
|
||||
..Default::default()
|
||||
};
|
||||
@@ -174,13 +184,12 @@ pub async fn add(
|
||||
|
||||
let item = item.insert(&ctx.db).await?;
|
||||
|
||||
let file_path = file_path.ok_or_else(|| ModelError::Any("File path is required".into()))?;
|
||||
let content = content.ok_or_else(|| ModelError::Any("Content is required".into()))?;
|
||||
|
||||
match ctx
|
||||
.storage
|
||||
.as_ref()
|
||||
.upload(file_path.as_path(), &content)
|
||||
.upload(path.as_path(), &content)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
@@ -232,7 +241,7 @@ pub async fn delete_data_by_id(id: i32, ctx: &AppContext) -> ModelResult<()> {
|
||||
|
||||
match data {
|
||||
Some(data) => {
|
||||
let path = PathBuf::from(&data.file_url);
|
||||
let path = PathBuf::from(&data.file_name);
|
||||
match ctx.storage.as_ref().delete(&path).await {
|
||||
Ok(_) => {}
|
||||
Err(_) => return Err(ModelError::Any("Failed to delete file from storage".into())),
|
||||
|
@@ -2,8 +2,8 @@ use loco_rs::prelude::*;
|
||||
use sea_orm::QueryOrder;
|
||||
|
||||
use crate::{models::{
|
||||
_entities::jobs::{Column, Entity, Model},
|
||||
jobs::JobWithTechnologies,
|
||||
_entities::jobs::{ActiveModel, Column, Entity, Model},
|
||||
jobs::{CreateJobForm, JobWithTechnologies},
|
||||
}, shared::get_technologies_from_string::get_technologies_from_string};
|
||||
|
||||
pub async fn get_all_jobs(ctx: &AppContext) -> Result<Vec<Model>> {
|
||||
@@ -32,3 +32,19 @@ pub async fn get_all_jobs_with_technologies(ctx: &AppContext) -> Result<Vec<JobW
|
||||
.collect();
|
||||
Ok(jobs_with_technologies)
|
||||
}
|
||||
|
||||
pub async fn create_job_from_form(ctx: &AppContext, job_data: &CreateJobForm) -> Result<Model> {
|
||||
let new_job = ActiveModel {
|
||||
company: Set(job_data.company.clone()),
|
||||
position: Set(job_data.position.clone()),
|
||||
start_date: Set(job_data.start_date.clone()),
|
||||
end_date: Set(job_data.end_date.clone()),
|
||||
technologies: Set(job_data.technologies.clone()),
|
||||
still_working: Set(job_data.still_working),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let job = new_job.insert(&ctx.db).await?;
|
||||
|
||||
Ok(job)
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use loco_rs::prelude::*;
|
||||
use sea_orm::QueryOrder;
|
||||
|
||||
use crate::models::_entities::skills::{Column, Entity, Model};
|
||||
use crate::models::_entities::skills::{ActiveModel, Column, Entity, Model};
|
||||
|
||||
pub async fn get_all_skills(ctx: &AppContext) -> Result<Vec<Model>> {
|
||||
let skills = Entity::find()
|
||||
@@ -9,3 +9,25 @@ pub async fn get_all_skills(ctx: &AppContext) -> Result<Vec<Model>> {
|
||||
.all(&ctx.db).await?;
|
||||
Ok(skills)
|
||||
}
|
||||
|
||||
pub async fn add_skill(ctx: &AppContext, name: String) -> Result<Model> {
|
||||
let new_skill = ActiveModel {
|
||||
name: Set(name),
|
||||
..Default::default()
|
||||
};
|
||||
let new_skill = new_skill.insert(&ctx.db).await?;
|
||||
Ok(new_skill)
|
||||
}
|
||||
|
||||
pub async fn add_skills(ctx: &AppContext, skills: Vec<String>) -> Result<Vec<Model>> {
|
||||
let mut new_skills = vec![];
|
||||
for skill in skills {
|
||||
let new_skill = ActiveModel {
|
||||
name: Set(skill),
|
||||
..Default::default()
|
||||
};
|
||||
let new_skill = new_skill.insert(&ctx.db).await?;
|
||||
new_skills.push(new_skill);
|
||||
}
|
||||
Ok(new_skills)
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
use loco_rs::prelude::*;
|
||||
|
||||
use crate::models::_entities::skills::ActiveModel;
|
||||
use crate::services::skills::add_skill;
|
||||
|
||||
pub struct CreateSkillData;
|
||||
|
||||
@@ -16,13 +16,7 @@ impl Task for CreateSkillData {
|
||||
async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> {
|
||||
let name = vars.cli_arg("name")?;
|
||||
|
||||
let mut item = ActiveModel {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
item.name = Set(name.to_string());
|
||||
|
||||
let item = item.insert(&app_context.db).await?;
|
||||
let item = add_skill(app_context, name.to_string()).await?;
|
||||
|
||||
tracing::info!(
|
||||
skill_id = item.id,
|
||||
|
70
src/tasks/import_skills.rs
Normal file
70
src/tasks/import_skills.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use loco_rs::prelude::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::services::skills::add_skills;
|
||||
|
||||
pub struct ImportSkills;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Skill {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Skills {
|
||||
skills: Vec<Skill>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Task for ImportSkills {
|
||||
fn task(&self) -> TaskInfo {
|
||||
TaskInfo {
|
||||
name: "import_skills".to_string(),
|
||||
detail: "Task for importing skills from json file or stdin".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> {
|
||||
let from_file = vars.cli_arg("from_file").ok().map(|v| v.parse::<bool>().unwrap_or(false)).unwrap_or(false);
|
||||
|
||||
match from_file {
|
||||
true => {
|
||||
let file_path = vars.cli_arg("file_path")?;
|
||||
|
||||
let data = std::fs::read_to_string(file_path)?;
|
||||
let skills: Skills = serde_json::from_str(&data)?;
|
||||
process_skills(app_context, skills).await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
false => {
|
||||
let raw_data = vars.cli_arg("raw_data")?;
|
||||
let skills: Skills = get_skills_from_raw_data(&raw_data);
|
||||
process_skills(app_context, skills).await?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn get_skills_from_raw_data(raw_data: &str) -> Skills {
|
||||
let skills = raw_data.split(',').map(|s| Skill { name: s.to_string() }).collect();
|
||||
Skills { skills }
|
||||
}
|
||||
|
||||
async fn process_skills(app_context: &AppContext, skills: Skills) -> Result<()> {
|
||||
let skills_names = skills.skills.iter().map(|s| s.name.clone()).collect();
|
||||
let items = add_skills(app_context, skills_names).await?;
|
||||
|
||||
for item in items {
|
||||
tracing::info!(
|
||||
skill_id = item.id,
|
||||
skill_name = &item.name,
|
||||
"Skill created successfully",
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -6,3 +6,4 @@ pub mod seed;
|
||||
pub mod clear_data;
|
||||
pub mod delete_data;
|
||||
pub mod delete_project;
|
||||
pub mod import_skills;
|
5
src/views/job.rs
Normal file
5
src/views/job.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use loco_rs::prelude::*;
|
||||
|
||||
pub async fn create_job(v: impl ViewRenderer) -> Result<impl IntoResponse> {
|
||||
format::render().view(&v, "website/create-job.html", data!({}))
|
||||
}
|
@@ -2,3 +2,4 @@ pub mod auth;
|
||||
pub mod data;
|
||||
pub mod website;
|
||||
pub mod projects;
|
||||
pub mod job;
|
@@ -6,7 +6,25 @@ module.exports = {
|
||||
'./assets/static/js/*.js',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
'aero-sky-light': '#74d2ff',
|
||||
'aero-sky-dark': '#008cff',
|
||||
'aero-grass-light': '#a1ff8b',
|
||||
'aero-grass-dark': '#38c172',
|
||||
'aero-glass': 'rgba(255, 255, 255, 0.3)',
|
||||
'aero-border': 'rgba(255, 255, 255, 0.5)',
|
||||
},
|
||||
backgroundImage: {
|
||||
'aero-gradient': 'linear-gradient(to bottom, #74d2ff, #008cff)',
|
||||
'gloss-gradient': 'linear-gradient(to bottom, rgba(255,255,255,0.6), rgba(255,255,255,0.1))',
|
||||
},
|
||||
borderRadius: {
|
||||
'aero': '12px',
|
||||
},
|
||||
boxShadow: {
|
||||
'aero': '0 4px 30px rgba(0, 0, 0, 0.1)',
|
||||
},},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography'), require('tailwindcss-motion')],
|
||||
};
|
||||
|
Reference in New Issue
Block a user