Compare commits

...

10 Commits

Author SHA1 Message Date
Nikola Petrov
041772e67a remove /mail 2024-11-03 13:51:44 +01:00
Nikola Petrov
362c411751 Update About me Add Readme 2024-11-03 13:20:06 +01:00
Nikola Petrov
0727d5f71a Remove cash 2024-11-01 15:33:15 +01:00
Nikola Petrov
ea3167981c Up cv 2024-09-23 23:28:26 +02:00
Nikola Petrov
4667dfee78 Update import paths 2024-09-23 23:24:59 +02:00
Nikola Petrov
93241aae82 Update userKnowledge 2024-09-23 23:24:20 +02:00
Nikola Petrov
f3df4b9913 update main 2024-08-26 19:25:51 +02:00
Nikola Petrov
b64e7d8b71 up frontend list
when add new media reload all media
2024-08-04 11:04:08 +02:00
Nikola Petrov
113b9f87f6 Update project links 2024-08-02 11:39:40 +02:00
Nikola Petrov
0a8e3fc32a up_temp.env 2024-08-01 15:02:10 +02:00
21 changed files with 75 additions and 460 deletions

5
Readme.md Normal file
View File

@ -0,0 +1,5 @@
## Build
```
bun run build.ts
bun run build_app
```

View File

@ -1,7 +1,4 @@
async function build() {
const minify = {
whitespace: true,
@ -10,15 +7,12 @@ async function build() {
}
const sa = await Bun.build({
entrypoints: ["./frontend/cash/cash", "./frontend/list/list"],
entrypoints: ["./frontend/list/list"],
outdir: "./public/assets/build/",
minify,
})
console.log(sa);
const { stdout } = Bun.spawnSync({ cmd: ["bun", "build", "./app.ts", "--outfile=bundle.js", "--target=bun", "--minify"] });
console.log(stdout.toString());
}
build();

View File

@ -1,97 +0,0 @@
import { type Request, type Response } from "express";
import cashTransactionModel from '../models/cashTransactionModel';
const types = ['ZAVRNITEV POS NAKUP', 'POS NAKUP', 'BA DVIG', 'priliv', 'SDD', 'SPLET/TEL NAKUP', 'PREDAVTORIZACIJE'];
// TODO hendel this PREDAVTORIZACIJA
export default {
list: async function (req: Request, res: Response) {
try {
var transactions;
const date = req.body.date;
if (date) {
const splitDate = date.split('-');
var year = splitDate[0];
var month = splitDate[1];
year = parseInt(year);
month = parseInt(month);
transactions = await cashTransactionModel.findWithPar(year, month);
} else {
transactions = await cashTransactionModel.find();
}
const data = { transactions: transactions, types: types };
return res.json(data);
} catch (err) {
return res.status(500).json({
message: 'Error when getting transactions.',
error: err
});
};
},
create: async function (req: Request, res: Response) {
const rawString = req.body.messageBody;
if (rawString == "") return res.status(400).json({ message: "empty string" });
const transaction = {
rawMessage: rawString,
day: 0,
month: 0,
year: 0,
amount: 0,
type: -1,
company: "",
};
const datePattern = /(0[1-9]|[12][0-9]|3[01])\.(0[1-9]|1[0-2])\.(19|20)\d{2}/;
const amountPattern = /\d{1,3}(?:[.]\d{3})*(?:[.,]\d{2})(?=\sEUR)/;
const companyPattern1 = /(?<=(UR,|\sod)\s).*?(?=\s(na\s|za\s|Inf))/;
for (var i = 0; i < types.length; i++) {
if (rawString.includes(types[i])) {
transaction.type = i;
break;
}
}
if (transaction.type != -1) {
const amountMatch = rawString.match(amountPattern);
if (amountMatch) {
const amount = amountMatch[0].replace('.', '').replace(',', '.');
transaction.amount = parseFloat(amount);
}
const companyMatch1 = rawString.match(companyPattern1);
if (companyMatch1) transaction.company = companyMatch1[0];
const dateMatch = rawString.match(datePattern);
if (dateMatch) {
const date = dateMatch[0].split('.');
transaction.day = date[0];
transaction.month = date[1];
transaction.year = date[2];
}
}
const trans = await cashTransactionModel.save(transaction.rawMessage, transaction.day, transaction.month, transaction.year, transaction.amount, transaction.type, transaction.company);
if (trans) {
return res.status(201).json(trans);
}
else {
return res.status(400).json({ message: "something went wrong" });
}
},
delete: async function (req: Request, res: Response) {
cashTransactionModel.findOneAndDelete(-1)
.then(data => {
res.status(201).json({ message: "OK" });
});
},
};

View File

@ -1,116 +0,0 @@
import Chart from 'chart.js/auto'
import * as elements from "../elementcreate";
interface Transaction {
day: number;
month: number;
year: number;
amount: number;
type: number;
company: string;
}
var types: Array<string> = [];
async function submitMedia(event: SubmitEvent) {
event.preventDefault();
const pass = document.getElementById("pass") as HTMLInputElement | null;
const date = document.getElementById("date") as HTMLInputElement | null;
if (!pass || !date) return;
if (pass.value == "") return;
try {
const result = await fetch('/api/cash/list', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ pass: pass.value, date: date.value }),
})
const data: { types: string[]; transactions: Array<Transaction>; } = await result.json();
types = data.types;
renderData(data.transactions);
renderChart(data.transactions);
} catch (e) {
console.log(e);
}
pass.value = "";
};
document.addEventListener('DOMContentLoaded', async () => {
document.getElementById("myform")?.addEventListener("submit", submitMedia);
});
function renderChart(transactions: Array<Transaction>) {
const cash = [];
for (let index = 0; index < 7; index++) {
cash.push(0);
}
for (let index = 0; index < transactions.length; index++) {
const element = transactions[index];
const type = element.type;
const amount = element.amount;
if (type == -1) continue;
cash[type] += amount;
}
var data = {
labels: types,
datasets: [{
label: '',
data: cash
}]
};
var ctx = document.getElementById('acquisitions') as HTMLCanvasElement | null;
if (ctx == null) return;
new Chart(
ctx,
{
type: 'pie',
data: data,
}
);
};
function getTypeName(type: number) {
if (type == -1) return "UNKNOWN";
return types[type];
}
function renderData(transactions: Array<Transaction>) {
var tbody = document.getElementById("tbody");
if (tbody == null) return;
tbody.innerHTML = "";
transactions
.sort((a, b) => a.day - b.day)
.sort((a, b) => a.month - b.month)
.sort((a, b) => a.year - b.year);
for (let index = 0; index < transactions.length; index++) {
const element = transactions[index];
var row =
<tr>
<th scope="row">{index}</th>
<td>{element.day}</td>
<td>{element.month}</td>
<td>{element.year}</td>
<td>{element.amount}</td>
<td>{getTypeName(element.type)}</td>
<td>{element.company}</td>
</tr>
tbody.appendChild(row);
}
};

View File

@ -1,5 +1,5 @@
import type { Attributes } from "../elementcreate";
import * as elements from "../elementcreate";
import type { Attributes } from "frontend/elementcreate";
import * as elements from "frontend/elementcreate";
function MediaElement(attributes: Attributes, contents: string[]) {
const ret = <div class="col media-element" id={attributes['id']}>

View File

@ -1,10 +1,7 @@
import { MediaElement, MyHeader, MediaContainer } from "./elements";
import { splitByTitle, splitByYear } from "./functions";
import * as elements from "../elementcreate";
import { MediaElement, MyHeader, MediaContainer } from "frontend/list/elements";
import { splitByTitle, splitByYear } from "frontend/list/functions";
import * as elements from "frontend/elementcreate";
var sortType = 0;
var listType = 0;
@ -36,6 +33,16 @@ function getLink(): string {
return "/api/media/movies";
}
async function reload() {
try {
const response = await fetch(getLink());
const movies = await response.json();
renderMedias(movies);
} catch (err) {
console.log(err);
}
}
function submitMedia(event: SubmitEvent) {
event.preventDefault();
@ -62,21 +69,7 @@ function submitMedia(event: SubmitEvent) {
return;
}
const movie: Movie = await response.json();
var letter = movie.title[0].toUpperCase();
if (!isNaN(parseInt(letter))) letter = "#";
const mediaElement = <MediaElement webImg={movie.webImg} title={movie.title} released={movie.released} id={movie.code} fun={removeMedia} imageError={onImgError} />;
const container = document.getElementById(letter);
if (!container) {
root?.appendChild(<MyHeader title={letter} />);
root?.appendChild(<MediaContainer id={letter}>{mediaElement}</MediaContainer>);
return
};
container.appendChild(mediaElement);
movieElements.push(mediaElement);
await reload();
})
.catch(err => {
console.log(err);
@ -122,9 +115,6 @@ function loadState() {
}
}
/**
* @param {number} type
*/
function changeType(type: number) {
listType = type;
loadPage();
@ -143,9 +133,6 @@ function changeType(type: number) {
history.replaceState({}, '', window.location.pathname + '?' + searchParams.toString());
}
/**
* @param {number} type
*/
function changeSort(type: number) {
sortType = type;
loadPage();
@ -216,13 +203,7 @@ async function loadPage() {
listButtons.forEach(button => button?.classList.remove("active"));
listButtons[listType]?.classList.add("active");
try {
const response = await fetch(getLink());
const movies = await response.json();
renderMedias(movies);
} catch (err) {
console.log(err);
}
await reload();
}
@ -270,6 +251,7 @@ function renderMedias(unsorted_movies: Array<Movie>) {
if (!root) return;
root.innerHTML = "";
movieElements = [];
const splitMovies = splitBySort(unsorted_movies);

View File

@ -1,5 +1,5 @@
import { type NextFunction, type Request, type Response } from "express";
import userModel, { values } from '../models/userModel';
import userModel, { values } from 'models/userModel';
async function checkAuthenticated(req: Request, res: Response, next: NextFunction) {
const pass = req.body.pass;

View File

@ -1,69 +0,0 @@
import { type ResultSetHeader, type RowDataPacket, type QueryOptions } from "mysql2"
import pool from '../miscellaneous/db'
interface CashTransaction extends RowDataPacket {
id?: number;
rawMessage?: string;
day?: number;
month?: number;
year?: number;
amount?: number;
type?: number;
company?: string;
}
async function save(rawMessage: string, day: number, month: number, year: number, amount: number, type: number, company: string): Promise<number> {
try {
const options: QueryOptions = {
sql: "INSERT INTO bankCardTransaction (raw, day, month, year, amount, type, company) VALUES (?,?,?,?,?,?,?)",
values: [rawMessage, day, month, year, amount, type, company]
};
const [result, fields] = await pool.query<ResultSetHeader>(options);
return result.affectedRows;
}
catch (err) {
console.log(err);
}
return 0;
}
async function find(): Promise<CashTransaction[]> {
try {
const [rows, fields] = await pool.query<CashTransaction[]>("SELECT * FROM bankCardTransaction;");
return rows;
}
catch (err) {
console.log(err);
}
return [];
}
async function findWithPar(month: number, year: number): Promise<CashTransaction[]> {
try {
const [rows, fields] = await pool.query<CashTransaction[]>("SELECT * FROM bankCardTransaction;");
return rows;
}
catch (err) {
console.log(err);
}
return [];
}
async function findOneAndDelete(type: number): Promise<number> {
try {
const [result, fields] = await pool.query<ResultSetHeader>("DELETE FROM bankCardTransaction WHERE type = ?;", [type]);
return result.affectedRows;
}
catch (err) {
console.log(err);
}
return 0;
}
export default {
find,
findWithPar,
save,
findOneAndDelete
};

View File

@ -1,5 +1,5 @@
import { type ResultSetHeader, type RowDataPacket, type QueryOptions } from "mysql2"
import pool from '../miscellaneous/db'
import pool from 'miscellaneous/db'
interface Media extends RowDataPacket {

View File

@ -1,5 +1,5 @@
import { type ResultSetHeader, type RowDataPacket } from "mysql2"
import pool from '../miscellaneous/db'
import pool from 'miscellaneous/db'
interface UserD extends RowDataPacket {
name?: string;

View File

@ -4,7 +4,7 @@
"private": true,
"scripts": {
"start": "bun ./app.ts",
"build": "bun build ./app.ts --outfile=bundle.js --target=bun"
"build_app": "bun build ./app.ts --outfile=bundle.js --target=bun"
},
"dependencies": {
"@types/express": "^4.17.21",

Binary file not shown.

View File

@ -7,19 +7,23 @@
"living_location": "Ljubljana, Slovenia",
"web_link": "https://petrovv.com",
"git_link": "https://git.petrovv.com",
"email": "nikolape7@gmail.com",
"email": "nikola@petrovv.com",
"about_me": [
"I am Nikola, currently pursuing my studies at the Faculty of Electrical Engineering and Computer Science (FERI) in Maribor. My academic journey is largely driven by my interest in application and web development. I find the process of creating functional and user-friendly digital solutions both challenging and rewarding. This field allows me to blend creativity with technical skills, which I find particularly engaging.",
"Recently, I have developed an interest in the game of Go. The strategic depth and complexity of the game have captivated my attention, providing a stimulating mental exercise. Additionally, I have started exploring photography. Capturing moments and expressing visual stories through a lens has become a newfound passion, offering a different kind of creative outlet that complements my technical pursuits."
],
"project": [
{
"img": "/images/projects/Advent_Of_Code_Logo.jpg",
"title": "Advent of code",
"des": "My solutions for AOC",
"link": "https://gitlab.com/homep/advent_of_code"
"link": "https://git.petrovv.com/home/advent_of_code"
},
{
"img": "/images/projects/password_manager.jpeg",
"title": "Password manager",
"des": "CLI app",
"link": "https://gitlab.com/homep/password_manager"
"link": "https://git.petrovv.com/home/password_manager"
},
{
"img": "/images/projects/list.jpeg",
@ -31,40 +35,46 @@
"img": "/images/logo.png",
"title": "Server",
"des": "Everything running on my server",
"link": "https://gitlab.com/homep/server"
"link": "https://git.petrovv.com/home/server"
},
{
"img": "/images/projects/projektna_naloga.jpeg",
"title": "Highway Tracker",
"des": "School project",
"link": "https://gitlab.com/school221/projektna_naloga"
"link": "https://git.petrovv.com/school/projektna_naloga"
},
{
"img": "/images/projects/media_player.jpeg",
"title": "Media player",
"des": "WPF",
"link": "https://gitlab.com/school221/semester_3/uporabniski_vmesniki"
"link": "https://git.petrovv.com/school/semester_3/uporabniski_vmesniki"
},
{
"img": "/images/projects/bitshift.jpeg",
"title": "BitShifters",
"des": "unity",
"link": "https://gitlab.com/school221/semester_4/razvoj_programskih_sistemov/bitshifters"
"link": "https://git.petrovv.com/school/semester_4/razvoj_programskih_sistemov/bitshifters"
},
{
"img": "/images/projects/tetris.jpeg",
"title": "Tetris",
"des": "WPF",
"link": "https://gitlab.com/school221/semester_4/razvoj_programskih_sistemov/tetrisrps"
"link": "https://git.petrovv.com/school/semester_4/razvoj_programskih_sistemov/tetrisrps"
},
{
"img": "/images/projects/games.jpeg",
"title": "Games",
"des": "java",
"link": "https://gitlab.com/school221/semester_5/uvod_v_razvoj_racunalniskih_iger"
"link": "https://git.petrovv.com/school/semester_5/uvod_v_razvoj_racunalniskih_iger"
}
],
"experience": [
{
"title": "Backend/Frontend",
"company": "RRC d.o.o",
"time": "01/09/2024 - CURRENT",
"des": "Backend in java with frontend in ext JS and jQuery"
},
{
"title": "Developer",
"company": "RRC d.o.o",

View File

@ -1,12 +1,10 @@
import express, { type Request, type Response } from "express";
import checkAuthenticated from '../../miscellaneous/checkAuthenticated';
import mediaRouter from './mediaRouter';
import cashTransactionRouter from './cashTransactionRouter';
import checkAuthenticated from 'miscellaneous/checkAuthenticated';
import mediaRouter from 'routes/api/mediaRouter';
const router = express.Router();
router.use('/media', mediaRouter);
router.use('/cash', checkAuthenticated, cashTransactionRouter);
router.get('/', function (req: Request, res: Response) {
res.status(200).json({ message: 'API is working' });

View File

@ -1,12 +0,0 @@
import express from "express";
import cashTransaction from '../../controllers/cashTransactionController';
const router = express.Router();
router.post('/list', cashTransaction.list);
router.post('/', cashTransaction.create);
router.delete('/', cashTransaction.delete);
export default router;

View File

@ -1,14 +1,11 @@
import express, { type Request, type Response } from "express";
import userData from './userKnowledge.json';
import userData from 'public/userKnowledge.json';
const router = express.Router();
/* GET home page. */
router.get('/', function (req: Request, res: Response) {
const project = userData.project;
const experience = userData.experience;
const education = userData.education;
res.render('main/2_0', { project, experience, education });
res.render('main/2_0', { userData });
});
router.get('/cv', function (req: Request, res: Response) {
@ -19,18 +16,10 @@ router.get('/old', function (req: Request, res: Response) {
res.render('main/1_0');
});
router.get('/mail', function (req: Request, res: Response) {
res.redirect('https://privateemail.com/');
});
router.get('/list', function (req: Request, res: Response) {
res.render('list');
});
router.get('/cash', function (req: Request, res: Response) {
res.render('cash');
});
//import userRouter from './user';
//router.use('/user', userRouter);

View File

@ -1,6 +1,6 @@
import express, { type Request, type Response } from "express";
import userController from '../controllers/userController';
import checkAuthenticated from '../miscellaneous/checkAuthenticated';
import userController from 'controllers/userController';
import checkAuthenticated from 'miscellaneous/checkAuthenticated';
const router = express.Router();

View File

@ -1,5 +1,5 @@
DBIP=
DBIP=''
DBPort=
DBUser=
DBPassword=
DBDatabase=
DBUser=''
DBPassword=''
DBDatabase=''

View File

@ -28,6 +28,7 @@
"noPropertyAccessFromIndexSignature": false,
"types": [
"bun-types"
]
],
"baseUrl": "./"
}
}

View File

@ -1,68 +0,0 @@
<!doctype html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="images/logo.ico" type="image/x-icon">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script>
<title>Cash</title>
<script type="module" src="/assets/build/cash/cash.js"></script>
</head>
<body>
<header>
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<form class="d-flex" action="" id="myform">
<input class="form-control me-2" type="date" name="date" id="date" placeholder="date">
<input class="form-control me-2" type="password" name="password" id="pass" placeholder="password">
<input class="btn btn-outline-success" type="submit" value="Submit">
</form>
</div>
</div>
</nav>
</header>
<main class="bg-body-tertiary pt-5 pb-5">
<div class="container position-relative">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Day</th>
<th scope="col">Month</th>
<th scope="col">Year</th>
<th scope="col">Amount</th>
<th scope="col">Type</th>
<th scope="col">Company</th>
</tr>
</thead>
<tbody id="tbody">
</tbody>
</table>
<div class="row">
<div class="w-50 mx-auto"><canvas id="acquisitions"></canvas></div>
</div>
</div>
</main>
</body>
</html>

View File

@ -17,6 +17,8 @@
<script defer nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>
</head>
{{#with userData}}
<body>
<main>
<aside class="sidebar" data-sidebar>
@ -26,9 +28,9 @@
</figure>
<div class="info-content">
<h1 class="name" title="Nikola Petrov">Nikola Petrov</h1>
<h1 class="name" title="Nikola Petrov">{{first_name}} {{last_name}}</h1>
<p class="title">RIT Student</p>
<p class="title">{{occupation}}</p>
</div>
<button class="info_more-btn" data-sidebar-btn>
@ -54,7 +56,7 @@
<div class="contact-info">
<p class="contact-title">Email</p>
<a href="mailto:nikola@petrov.nexus" class="contact-link">nikolape7@gmail.com</a>
<a href="mailto:{{email}}" class="contact-link">{{email}}</a>
</div>
</li>
@ -68,7 +70,7 @@
<div class="contact-info">
<p class="contact-title">Phone</p>
<a href="tel:0038670749506" class="contact-link">070749506</a>
<a href="tel:{{phone_number}}" class="contact-link">{{phone_number}}</a>
</div>
</li>
@ -82,7 +84,7 @@
<div class="contact-info">
<p class="contact-title">Birthday</p>
<time datetime="2000-11-01">November, 2000</time>
<time datetime="2000-11-01">{{birth}}</time>
</div>
</li>
@ -108,7 +110,7 @@
<ul class="social-list">
<li class="social-item">
<a href="https://gitlab.com/nikolape7" title="GitLab" class="social-link">
<a href="{{git_link}}" title="GitLab" class="social-link">
<ion-icon name="logo-gitlab"></ion-icon>
</a>
</li>
@ -178,18 +180,12 @@
<section class="about-text">
<p>
I am currently an RIT student at FERI in Maribor and come from the beautiful city of Ljubljana.
What really excites me is the opportunity to build systems that have a direct and positive impact on
people's
lives.
I find great satisfaction in using my skills to create practical solutions that address real-world
challenges.
</p>
<p>
I am excited by the potential of my work and the opportunities that lie ahead.
With my dedication and expertise, I am determined to contribute to the development of solutions that enhance
the people's experiences and make a positive difference in the world.
{{#each about_me}}
{{this}}
{{#unless @last}}
</br>
{{/unless}}
{{/each}}
</p>
</section>
@ -368,4 +364,6 @@
</body>
{{/with}}
</html>