This commit is contained in:
root 2026-03-22 13:45:44 +01:00
parent a2d6967a96
commit 4208ca4fe3
13 changed files with 2967 additions and 2966 deletions

1
z1/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.sh text eol=lf

View File

@ -1,12 +1,12 @@
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY backend ./backend
EXPOSE 3000
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY backend ./backend
EXPOSE 3000
CMD ["node", "backend/app.js"]

View File

@ -1,226 +1,226 @@
const express = require('express');
const mysql = require('mysql2/promise');
const bcrypt = require('bcryptjs');
const session = require('express-session');
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '../.env') });
const app = express();
const FRONTEND_DIR = path.join(__dirname, '../frontend');
const pool = mysql.createPool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}));
app.get('/', (req, res) => {
if (req.session.user) {
return res.redirect('/index.html');
}
res.sendFile(path.join(FRONTEND_DIR, 'login.html'));
});
app.get('/register', (req, res) => {
res.sendFile(path.join(FRONTEND_DIR, 'register.html'));
});
app.get('/index.html', (req, res) => {
if (!req.session.user) {
return res.redirect('/');
}
res.sendFile(path.join(FRONTEND_DIR, 'index.html'));
});
app.post('/register', async (req, res) => {
const { username, password, password2 } = req.body;
if (!username || !password || !password2) {
return res.redirect('/register.html?error=' + encodeURIComponent('Vyplň všetky polia'));
}
if (password !== password2) {
return res.redirect('/register.html?error=' + encodeURIComponent('Heslá sa nezhodujú'));
}
try {
const hashedPassword = await bcrypt.hash(password, 10);
await pool.execute(
'INSERT INTO users (username, password) VALUES (?, ?)',
[username, hashedPassword]
);
return res.redirect('/');
} catch (err) {
console.error(err);
return res.redirect('/register.html?error=' + encodeURIComponent('Používateľ už existuje'));
}
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.redirect('/?error=Vyplň%20všetky%20polia');
}
try {
const [rows] = await pool.execute(
'SELECT * FROM users WHERE username = ?',
[username]
);
const row = rows[0];
if (!row) {
return res.redirect('/?error=Zlé%20meno%20alebo%20heslo');
}
const result = await bcrypt.compare(password, row.password);
if (!result) {
return res.redirect('/?error=Zlé%20meno%20alebo%20heslo');
}
req.session.user = {
id: row.id,
username: row.username
};
return res.redirect('/index.html');
} catch (err) {
console.error(err);
return res.redirect('/?error=Chyba%20databázy');
}
});
app.post('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
app.get('/api/me', (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
res.json(req.session.user);
});
app.get('/api/tasks', async (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
try {
const [rows] = await pool.execute(
'SELECT * FROM tasks WHERE user_id = ? ORDER BY deadline ASC',
[req.session.user.id]
);
res.json(rows);
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Chyba pri načítaní úloh' });
}
});
app.post('/api/tasks', async (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
const { title, description, deadline } = req.body;
if (!title || !description || !deadline) {
return res.status(400).json({ error: 'Vyplň všetky polia' });
}
try {
const [result] = await pool.execute(
'INSERT INTO tasks (user_id, title, description, deadline) VALUES (?, ?, ?, ?)',
[req.session.user.id, title, description, deadline]
);
const [rows] = await pool.execute(
'SELECT * FROM tasks WHERE id = ?',
[result.insertId]
);
res.json(rows[0]);
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Chyba pri ukladaní úlohy' });
}
});
app.delete('/api/tasks/:id', async (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
try {
await pool.execute(
'DELETE FROM tasks WHERE id = ? AND user_id = ?',
[req.params.id, req.session.user.id]
);
res.json({ success: true });
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Chyba pri mazaní úlohy' });
}
});
app.patch('/api/tasks/:id/toggle', async (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
const { status } = req.body;
try {
await pool.execute(
'UPDATE tasks SET status = ? WHERE id = ? AND user_id = ?',
[status ? 1 : 0, req.params.id, req.session.user.id]
);
res.json({ success: true });
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Chyba pri zmene stavu úlohy' });
}
});
app.use(express.static(FRONTEND_DIR));
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
try {
const connection = await pool.getConnection();
console.log('Pripojenie na MySQL úspešné.');
connection.release();
} catch (err) {
console.error('Nepodarilo sa pripojiť na MySQL:', err.message);
}
console.log(`Server beží na http://localhost:${PORT}`);
const express = require('express');
const mysql = require('mysql2/promise');
const bcrypt = require('bcryptjs');
const session = require('express-session');
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '../.env') });
const app = express();
const FRONTEND_DIR = path.join(__dirname, '../frontend');
const pool = mysql.createPool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}));
app.get('/', (req, res) => {
if (req.session.user) {
return res.redirect('/index.html');
}
res.sendFile(path.join(FRONTEND_DIR, 'login.html'));
});
app.get('/register', (req, res) => {
res.sendFile(path.join(FRONTEND_DIR, 'register.html'));
});
app.get('/index.html', (req, res) => {
if (!req.session.user) {
return res.redirect('/');
}
res.sendFile(path.join(FRONTEND_DIR, 'index.html'));
});
app.post('/register', async (req, res) => {
const { username, password, password2 } = req.body;
if (!username || !password || !password2) {
return res.redirect('/register.html?error=' + encodeURIComponent('Vyplň všetky polia'));
}
if (password !== password2) {
return res.redirect('/register.html?error=' + encodeURIComponent('Heslá sa nezhodujú'));
}
try {
const hashedPassword = await bcrypt.hash(password, 10);
await pool.execute(
'INSERT INTO users (username, password) VALUES (?, ?)',
[username, hashedPassword]
);
return res.redirect('/');
} catch (err) {
console.error(err);
return res.redirect('/register.html?error=' + encodeURIComponent('Používateľ už existuje'));
}
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.redirect('/?error=Vyplň%20všetky%20polia');
}
try {
const [rows] = await pool.execute(
'SELECT * FROM users WHERE username = ?',
[username]
);
const row = rows[0];
if (!row) {
return res.redirect('/?error=Zlé%20meno%20alebo%20heslo');
}
const result = await bcrypt.compare(password, row.password);
if (!result) {
return res.redirect('/?error=Zlé%20meno%20alebo%20heslo');
}
req.session.user = {
id: row.id,
username: row.username
};
return res.redirect('/index.html');
} catch (err) {
console.error(err);
return res.redirect('/?error=Chyba%20databázy');
}
});
app.post('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
app.get('/api/me', (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
res.json(req.session.user);
});
app.get('/api/tasks', async (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
try {
const [rows] = await pool.execute(
'SELECT * FROM tasks WHERE user_id = ? ORDER BY deadline ASC',
[req.session.user.id]
);
res.json(rows);
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Chyba pri načítaní úloh' });
}
});
app.post('/api/tasks', async (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
const { title, description, deadline } = req.body;
if (!title || !description || !deadline) {
return res.status(400).json({ error: 'Vyplň všetky polia' });
}
try {
const [result] = await pool.execute(
'INSERT INTO tasks (user_id, title, description, deadline) VALUES (?, ?, ?, ?)',
[req.session.user.id, title, description, deadline]
);
const [rows] = await pool.execute(
'SELECT * FROM tasks WHERE id = ?',
[result.insertId]
);
res.json(rows[0]);
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Chyba pri ukladaní úlohy' });
}
});
app.delete('/api/tasks/:id', async (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
try {
await pool.execute(
'DELETE FROM tasks WHERE id = ? AND user_id = ?',
[req.params.id, req.session.user.id]
);
res.json({ success: true });
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Chyba pri mazaní úlohy' });
}
});
app.patch('/api/tasks/:id/toggle', async (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Neprihlásený používateľ' });
}
const { status } = req.body;
try {
await pool.execute(
'UPDATE tasks SET status = ? WHERE id = ? AND user_id = ?',
[status ? 1 : 0, req.params.id, req.session.user.id]
);
res.json({ success: true });
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Chyba pri zmene stavu úlohy' });
}
});
app.use(express.static(FRONTEND_DIR));
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
try {
const connection = await pool.getConnection();
console.log('Pripojenie na MySQL úspešné.');
connection.release();
} catch (err) {
console.error('Nepodarilo sa pripojiť na MySQL:', err.message);
}
console.log(`Server beží na http://localhost:${PORT}`);
});

View File

@ -1,15 +1,15 @@
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
deadline DATE NOT NULL,
status TINYINT(1) NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
deadline DATE NOT NULL,
status TINYINT(1) NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

View File

@ -1,42 +1,42 @@
services:
db:
image: mysql:8.0
container_name: todo_db
restart: always
environment:
MYSQL_ROOT_PASSWORD: NajsilnejsieHeslo123.
MYSQL_DATABASE: zkt_zadanie
MYSQL_USER: todo_user
MYSQL_PASSWORD: SilneHeslo123.
volumes:
- todo_mysql_data:/var/lib/mysql
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3307:3306"
backend:
build:
context: .
dockerfile: backend/Dockerfile
container_name: todo_backend
restart: always
env_file:
- .env
depends_on:
- db
ports:
- "3000:3000"
frontend:
build:
context: .
dockerfile: frontend/Dockerfile
container_name: todo_frontend
restart: always
depends_on:
- backend
ports:
- "8080:80"
volumes:
services:
db:
image: mysql:8.0
container_name: todo_db
restart: always
environment:
MYSQL_ROOT_PASSWORD: NajsilnejsieHeslo123.
MYSQL_DATABASE: zkt_zadanie
MYSQL_USER: todo_user
MYSQL_PASSWORD: SilneHeslo123.
volumes:
- todo_mysql_data:/var/lib/mysql
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3307:3306"
backend:
build:
context: .
dockerfile: backend/Dockerfile
container_name: todo_backend
restart: always
env_file:
- .env
depends_on:
- db
ports:
- "3000:3000"
frontend:
build:
context: .
dockerfile: frontend/Dockerfile
container_name: todo_frontend
restart: always
depends_on:
- backend
ports:
- "8080:80"
volumes:
todo_mysql_data:

View File

@ -1,9 +1,9 @@
FROM nginx:alpine
COPY frontend/index.html /usr/share/nginx/html/index.html
COPY frontend/login.html /usr/share/nginx/html/login.html
COPY frontend/register.html /usr/share/nginx/html/register.html
COPY frontend/generovanie_karticiek.js /usr/share/nginx/html/generovanie_karticiek.js
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
FROM nginx:alpine
COPY frontend/index.html /usr/share/nginx/html/index.html
COPY frontend/login.html /usr/share/nginx/html/login.html
COPY frontend/register.html /usr/share/nginx/html/register.html
COPY frontend/generovanie_karticiek.js /usr/share/nginx/html/generovanie_karticiek.js
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

View File

@ -1,209 +1,209 @@
const emptyMessage = document.getElementById("emptyMessage");
const cardContainer = document.getElementById("cardContainer");
const addCardBtn = document.getElementById("addCardBtn");
const taskModal = document.getElementById("taskModal");
const saveTaskBtn = document.getElementById("saveTaskBtn");
const cancelTaskBtn = document.getElementById("cancelTaskBtn");
const filterDate = document.getElementById("filterDate");
const loggedUser = document.getElementById("loggedUser");
let tasks = [];
function formatDate(isoDate) {
if (!isoDate) return "bez termínu";
return new Date(isoDate).toLocaleDateString("sk-SK", {
day: "2-digit",
month: "2-digit",
year: "numeric"
});
}
function updateEmptyVisibility() {
if (tasks.length === 0) {
emptyMessage.classList.remove("hidden");
} else {
emptyMessage.classList.add("hidden");
}
}
function createCard(task) {
const card = document.createElement("div");
card.className = "p-6 rounded-xl shadow-md border hover:shadow-lg transition-all duration-200";
card.dataset.deadline = task.deadline || "";
card.dataset.id = task.id;
if (task.status) {
card.classList.add("bg-green-100", "border-green-200");
} else {
card.classList.add("bg-yellow-100", "border-yellow-200");
}
card.innerHTML = `
<h3 class="font-bold text-lg mb-2 text-gray-800">${task.title}</h3>
<p class="text-gray-700 mb-4">${task.description}</p>
<p class="text-sm text-gray-600 mb-5">Termín: ${formatDate(task.deadline)}</p>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input type="checkbox" class="task-checkbox h-5 w-5 text-green-600 rounded border-gray-300 focus:ring-green-500" ${task.status ? "checked" : ""}>
<label class="ml-2 text-sm text-gray-700 cursor-pointer">Hotovo</label>
</div>
<button class="delete-btn px-5 py-2 bg-red-600 text-white text-sm rounded-lg hover:bg-red-700 transition">
Zmazať
</button>
</div>
`;
const checkbox = card.querySelector(".task-checkbox");
checkbox.addEventListener("change", async () => {
const newValue = checkbox.checked ? 1 : 0;
try {
const response = await fetch(`/api/tasks/${task.id}/toggle`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ status: newValue })
});
if (!response.ok) {
console.error("PATCH neprešiel:", response.status);
checkbox.checked = !checkbox.checked;
return;
}
task.status = newValue;
card.classList.remove(
"bg-yellow-100",
"border-yellow-200",
"bg-green-100",
"border-green-200"
);
if (newValue) {
card.classList.add("bg-green-100", "border-green-200");
} else {
card.classList.add("bg-yellow-100", "border-yellow-200");
}
} catch (error) {
console.error("Chyba fetchu:", error);
checkbox.checked = !checkbox.checked;
}
});
const deleteBtn = card.querySelector(".delete-btn");
deleteBtn.addEventListener("click", async () => {
try {
const response = await fetch(`/api/tasks/${task.id}`, {
method: "DELETE"
});
if (response.ok) {
tasks = tasks.filter(t => t.id !== task.id);
renderTasks();
}
} catch (error) {
console.error("Chyba pri mazaní úlohy:", error);
}
});
cardContainer.appendChild(card);
}
function renderTasks() {
const order = filterDate.value;
const sortedTasks = [...tasks].sort((a, b) => {
const da = a.deadline ? new Date(a.deadline) : new Date("9999-12-31");
const db = b.deadline ? new Date(b.deadline) : new Date("9999-12-31");
return order === "asc" ? da - db : db - da;
});
cardContainer.innerHTML = "";
sortedTasks.forEach(createCard);
updateEmptyVisibility();
}
function showModal() {
taskModal.classList.remove("hidden");
document.getElementById("taskTitle").value = "";
document.getElementById("taskDescription").value = "";
document.getElementById("taskDeadline").value = "";
}
function hideModal() {
taskModal.classList.add("hidden");
}
async function loadUser() {
try {
const response = await fetch("/api/me");
if (!response.ok) {
window.location.href = "/login.html";
return;
}
const user = await response.json();
loggedUser.textContent = `Prihlásený ako: ${user.username}`;
} catch (error) {
console.error("Chyba pri načítaní používateľa:", error);
window.location.href = "/login.html";
}
}
async function loadTasks() {
try {
const response = await fetch("/api/tasks");
if (!response.ok) {
window.location.href = "/login.html";
return;
}
tasks = await response.json();
renderTasks();
} catch (error) {
console.error("Chyba pri načítaní úloh:", error);
}
}
addCardBtn.addEventListener("click", showModal);
cancelTaskBtn.addEventListener("click", hideModal);
saveTaskBtn.addEventListener("click", async () => {
const title = document.getElementById("taskTitle").value.trim();
const description = document.getElementById("taskDescription").value.trim();
const deadline = document.getElementById("taskDeadline").value;
if (!title || !description || !deadline) {
return;
}
try {
const response = await fetch("/api/tasks", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ title, description, deadline })
});
if (response.ok) {
const newTask = await response.json();
tasks.push(newTask);
renderTasks();
hideModal();
}
} catch (error) {
console.error("Chyba pri pridávaní úlohy:", error);
}
});
filterDate.addEventListener("change", renderTasks);
loadUser();
const emptyMessage = document.getElementById("emptyMessage");
const cardContainer = document.getElementById("cardContainer");
const addCardBtn = document.getElementById("addCardBtn");
const taskModal = document.getElementById("taskModal");
const saveTaskBtn = document.getElementById("saveTaskBtn");
const cancelTaskBtn = document.getElementById("cancelTaskBtn");
const filterDate = document.getElementById("filterDate");
const loggedUser = document.getElementById("loggedUser");
let tasks = [];
function formatDate(isoDate) {
if (!isoDate) return "bez termínu";
return new Date(isoDate).toLocaleDateString("sk-SK", {
day: "2-digit",
month: "2-digit",
year: "numeric"
});
}
function updateEmptyVisibility() {
if (tasks.length === 0) {
emptyMessage.classList.remove("hidden");
} else {
emptyMessage.classList.add("hidden");
}
}
function createCard(task) {
const card = document.createElement("div");
card.className = "p-6 rounded-xl shadow-md border hover:shadow-lg transition-all duration-200";
card.dataset.deadline = task.deadline || "";
card.dataset.id = task.id;
if (task.status) {
card.classList.add("bg-green-100", "border-green-200");
} else {
card.classList.add("bg-yellow-100", "border-yellow-200");
}
card.innerHTML = `
<h3 class="font-bold text-lg mb-2 text-gray-800">${task.title}</h3>
<p class="text-gray-700 mb-4">${task.description}</p>
<p class="text-sm text-gray-600 mb-5">Termín: ${formatDate(task.deadline)}</p>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input type="checkbox" class="task-checkbox h-5 w-5 text-green-600 rounded border-gray-300 focus:ring-green-500" ${task.status ? "checked" : ""}>
<label class="ml-2 text-sm text-gray-700 cursor-pointer">Hotovo</label>
</div>
<button class="delete-btn px-5 py-2 bg-red-600 text-white text-sm rounded-lg hover:bg-red-700 transition">
Zmazať
</button>
</div>
`;
const checkbox = card.querySelector(".task-checkbox");
checkbox.addEventListener("change", async () => {
const newValue = checkbox.checked ? 1 : 0;
try {
const response = await fetch(`/api/tasks/${task.id}/toggle`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ status: newValue })
});
if (!response.ok) {
console.error("PATCH neprešiel:", response.status);
checkbox.checked = !checkbox.checked;
return;
}
task.status = newValue;
card.classList.remove(
"bg-yellow-100",
"border-yellow-200",
"bg-green-100",
"border-green-200"
);
if (newValue) {
card.classList.add("bg-green-100", "border-green-200");
} else {
card.classList.add("bg-yellow-100", "border-yellow-200");
}
} catch (error) {
console.error("Chyba fetchu:", error);
checkbox.checked = !checkbox.checked;
}
});
const deleteBtn = card.querySelector(".delete-btn");
deleteBtn.addEventListener("click", async () => {
try {
const response = await fetch(`/api/tasks/${task.id}`, {
method: "DELETE"
});
if (response.ok) {
tasks = tasks.filter(t => t.id !== task.id);
renderTasks();
}
} catch (error) {
console.error("Chyba pri mazaní úlohy:", error);
}
});
cardContainer.appendChild(card);
}
function renderTasks() {
const order = filterDate.value;
const sortedTasks = [...tasks].sort((a, b) => {
const da = a.deadline ? new Date(a.deadline) : new Date("9999-12-31");
const db = b.deadline ? new Date(b.deadline) : new Date("9999-12-31");
return order === "asc" ? da - db : db - da;
});
cardContainer.innerHTML = "";
sortedTasks.forEach(createCard);
updateEmptyVisibility();
}
function showModal() {
taskModal.classList.remove("hidden");
document.getElementById("taskTitle").value = "";
document.getElementById("taskDescription").value = "";
document.getElementById("taskDeadline").value = "";
}
function hideModal() {
taskModal.classList.add("hidden");
}
async function loadUser() {
try {
const response = await fetch("/api/me");
if (!response.ok) {
window.location.href = "/login.html";
return;
}
const user = await response.json();
loggedUser.textContent = `Prihlásený ako: ${user.username}`;
} catch (error) {
console.error("Chyba pri načítaní používateľa:", error);
window.location.href = "/login.html";
}
}
async function loadTasks() {
try {
const response = await fetch("/api/tasks");
if (!response.ok) {
window.location.href = "/login.html";
return;
}
tasks = await response.json();
renderTasks();
} catch (error) {
console.error("Chyba pri načítaní úloh:", error);
}
}
addCardBtn.addEventListener("click", showModal);
cancelTaskBtn.addEventListener("click", hideModal);
saveTaskBtn.addEventListener("click", async () => {
const title = document.getElementById("taskTitle").value.trim();
const description = document.getElementById("taskDescription").value.trim();
const deadline = document.getElementById("taskDeadline").value;
if (!title || !description || !deadline) {
return;
}
try {
const response = await fetch("/api/tasks", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ title, description, deadline })
});
if (response.ok) {
const newTask = await response.json();
tasks.push(newTask);
renderTasks();
hideModal();
}
} catch (error) {
console.error("Chyba pri pridávaní úlohy:", error);
}
});
filterDate.addEventListener("change", renderTasks);
loadUser();
loadTasks();

View File

@ -1,94 +1,94 @@
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do aplikácia</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E");
background-position: right 0.75rem center;
background-repeat: no-repeat;
background-size: 1.25em;
}
</style>
</head>
<body class="flex flex-col min-h-screen bg-gray-100">
<header class="bg-orange-500 h-20 flex justify-between items-center shadow-md px-6">
<h1 class="text-white font-bold text-2xl">To-Do aplikácia</h1>
<div class="flex items-center gap-4">
<span id="loggedUser" class="text-white font-medium"></span>
<form action="/logout" method="POST">
<button
type="submit"
class="bg-red-600 hover:bg-red-700 text-white font-medium px-4 py-2 rounded-lg transition"
>
Odhlásiť sa
</button>
</form>
</div>
</header>
<main class="flex-1 p-6 flex flex-col items-center bg-gray-50">
<div class="w-full max-w-3xl mb-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div class="flex items-center gap-3">
<div class="relative">
<select id="filterDate" class="pl-10 pr-4 py-2.5 bg-white border border-gray-300 rounded-lg shadow-sm focus:border-orange-400 focus:ring-orange-400 appearance-none">
<option value="asc">Najskôr najbližší termín</option>
<option value="desc">Najskôr najvzdialenejší termín</option>
</select>
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
</div>
<button id="addCardBtn" class="px-7 py-3.5 bg-orange-600 hover:bg-orange-700 text-white font-semibold rounded-full shadow-lg transition-all transform hover:scale-105 flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Pridať úlohu
</button>
</div>
<div id="emptyMessage" class="hidden text-center py-16 w-full max-w-3xl">
<p class="text-2xl font-medium text-gray-500 mb-6">Zatiaľ žiadne úlohy</p>
</div>
<div id="cardContainer" class="w-full max-w-3xl space-y-5">
</div>
</main>
<footer class="bg-orange-500 h-16 flex justify-center items-center text-white font-medium shadow-inner">
<div>By Michal</div>
</footer>
<div id="taskModal" class="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl p-8 w-full max-w-md shadow-2xl">
<h2 class="text-2xl font-bold mb-6 text-gray-800">Nová úloha</h2>
<label for="taskTitle" class="block text-sm font-medium text-gray-700 mb-1">Názov</label>
<input id="taskTitle" type="text" class="w-full p-3 border border-gray-300 rounded-lg mb-4 focus:outline-none focus:ring-2 focus:ring-orange-400" placeholder="Zadajte názov úlohy">
<label for="taskDescription" class="block text-sm font-medium text-gray-700 mb-1">Popis</label>
<textarea id="taskDescription" class="w-full p-3 border border-gray-300 rounded-lg mb-4 focus:outline-none focus:ring-2 focus:ring-orange-400" rows="4" placeholder="Detailnejší popis..."></textarea>
<label for="taskDeadline" class="block text-sm font-medium text-gray-700 mb-1">Termín dokončenia</label>
<input id="taskDeadline" type="date" class="w-full p-3 border border-gray-300 rounded-lg mb-6 focus:outline-none focus:ring-2 focus:ring-orange-400">
<div class="flex justify-end gap-3">
<button id="cancelTaskBtn" class="px-5 py-2.5 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition">Zrušiť</button>
<button id="saveTaskBtn" class="px-5 py-2.5 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">Uložiť</button>
</div>
</div>
</div>
<script src="generovanie_karticiek.js"></script>
</body>
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do aplikácia</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E");
background-position: right 0.75rem center;
background-repeat: no-repeat;
background-size: 1.25em;
}
</style>
</head>
<body class="flex flex-col min-h-screen bg-gray-100">
<header class="bg-orange-500 h-20 flex justify-between items-center shadow-md px-6">
<h1 class="text-white font-bold text-2xl">To-Do aplikácia</h1>
<div class="flex items-center gap-4">
<span id="loggedUser" class="text-white font-medium"></span>
<form action="/logout" method="POST">
<button
type="submit"
class="bg-red-600 hover:bg-red-700 text-white font-medium px-4 py-2 rounded-lg transition"
>
Odhlásiť sa
</button>
</form>
</div>
</header>
<main class="flex-1 p-6 flex flex-col items-center bg-gray-50">
<div class="w-full max-w-3xl mb-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div class="flex items-center gap-3">
<div class="relative">
<select id="filterDate" class="pl-10 pr-4 py-2.5 bg-white border border-gray-300 rounded-lg shadow-sm focus:border-orange-400 focus:ring-orange-400 appearance-none">
<option value="asc">Najskôr najbližší termín</option>
<option value="desc">Najskôr najvzdialenejší termín</option>
</select>
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
</div>
<button id="addCardBtn" class="px-7 py-3.5 bg-orange-600 hover:bg-orange-700 text-white font-semibold rounded-full shadow-lg transition-all transform hover:scale-105 flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
Pridať úlohu
</button>
</div>
<div id="emptyMessage" class="hidden text-center py-16 w-full max-w-3xl">
<p class="text-2xl font-medium text-gray-500 mb-6">Zatiaľ žiadne úlohy</p>
</div>
<div id="cardContainer" class="w-full max-w-3xl space-y-5">
</div>
</main>
<footer class="bg-orange-500 h-16 flex justify-center items-center text-white font-medium shadow-inner">
<div>By Michal</div>
</footer>
<div id="taskModal" class="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl p-8 w-full max-w-md shadow-2xl">
<h2 class="text-2xl font-bold mb-6 text-gray-800">Nová úloha</h2>
<label for="taskTitle" class="block text-sm font-medium text-gray-700 mb-1">Názov</label>
<input id="taskTitle" type="text" class="w-full p-3 border border-gray-300 rounded-lg mb-4 focus:outline-none focus:ring-2 focus:ring-orange-400" placeholder="Zadajte názov úlohy">
<label for="taskDescription" class="block text-sm font-medium text-gray-700 mb-1">Popis</label>
<textarea id="taskDescription" class="w-full p-3 border border-gray-300 rounded-lg mb-4 focus:outline-none focus:ring-2 focus:ring-orange-400" rows="4" placeholder="Detailnejší popis..."></textarea>
<label for="taskDeadline" class="block text-sm font-medium text-gray-700 mb-1">Termín dokončenia</label>
<input id="taskDeadline" type="date" class="w-full p-3 border border-gray-300 rounded-lg mb-6 focus:outline-none focus:ring-2 focus:ring-orange-400">
<div class="flex justify-end gap-3">
<button id="cancelTaskBtn" class="px-5 py-2.5 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition">Zrušiť</button>
<button id="saveTaskBtn" class="px-5 py-2.5 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">Uložiť</button>
</div>
</div>
</div>
<script src="generovanie_karticiek.js"></script>
</body>
</html>

View File

@ -1,77 +1,77 @@
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Prihlásenie</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-gray-100 flex items-center justify-center p-4">
<div class="w-full max-w-sm bg-white rounded-xl shadow-lg p-8">
<h1 class="text-3xl font-bold text-center text-gray-800 mb-8">
Prihlásenie
</h1>
<form id="loginForm" action="/login" method="POST" class="space-y-6">
<div>
<label for="meno" class="block text-sm font-medium text-gray-700 mb-1">
Meno / používateľ
</label>
<input
type="text"
id="meno"
name="username"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="Zadaj svoje meno"
/>
</div>
<div>
<label for="heslo" class="block text-sm font-medium text-gray-700 mb-1">
Heslo
</label>
<input
type="password"
id="heslo"
name="password"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="••••••••"
/>
</div>
<button
type="submit"
class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 rounded-lg transition duration-200"
>
Prihlásiť sa
</button>
</form>
<div class="text-center mt-6 text-sm text-gray-600">
Ešte nemáš účet?
<a href="/register.html" class="text-orange-600 hover:underline font-medium">
Zaregistruj sa
</a>
</div>
<p id="error" class="hidden mt-4 text-sm text-red-700 bg-red-50 border border-red-200 rounded-lg px-4 py-3 text-center"></p>
</div>
<script>
const params = new URLSearchParams(window.location.search);
const error = params.get('error');
const errorBox = document.getElementById('error');
if (error) {
errorBox.textContent = error;
errorBox.classList.remove('hidden');
}
</script>
</body>
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Prihlásenie</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-gray-100 flex items-center justify-center p-4">
<div class="w-full max-w-sm bg-white rounded-xl shadow-lg p-8">
<h1 class="text-3xl font-bold text-center text-gray-800 mb-8">
Prihlásenie
</h1>
<form id="loginForm" action="/login" method="POST" class="space-y-6">
<div>
<label for="meno" class="block text-sm font-medium text-gray-700 mb-1">
Meno / používateľ
</label>
<input
type="text"
id="meno"
name="username"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="Zadaj svoje meno"
/>
</div>
<div>
<label for="heslo" class="block text-sm font-medium text-gray-700 mb-1">
Heslo
</label>
<input
type="password"
id="heslo"
name="password"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="••••••••"
/>
</div>
<button
type="submit"
class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 rounded-lg transition duration-200"
>
Prihlásiť sa
</button>
</form>
<div class="text-center mt-6 text-sm text-gray-600">
Ešte nemáš účet?
<a href="/register.html" class="text-orange-600 hover:underline font-medium">
Zaregistruj sa
</a>
</div>
<p id="error" class="hidden mt-4 text-sm text-red-700 bg-red-50 border border-red-200 rounded-lg px-4 py-3 text-center"></p>
</div>
<script>
const params = new URLSearchParams(window.location.search);
const error = params.get('error');
const errorBox = document.getElementById('error');
if (error) {
errorBox.textContent = error;
errorBox.classList.remove('hidden');
}
</script>
</body>
</html>

View File

@ -1,42 +1,42 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index login.html;
location = / {
try_files /login.html =404;
}
location /api/ {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /login {
proxy_pass http://backend:3000/login;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location = /register {
proxy_pass http://backend:3000/register;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location = /logout {
proxy_pass http://backend:3000/logout;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location / {
try_files $uri $uri/ =404;
}
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index login.html;
location = / {
try_files /login.html =404;
}
location /api/ {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /login {
proxy_pass http://backend:3000/login;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location = /register {
proxy_pass http://backend:3000/register;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location = /logout {
proxy_pass http://backend:3000/logout;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
location / {
try_files $uri $uri/ =404;
}
}

View File

@ -1,91 +1,91 @@
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Registrácia</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-gray-100 flex items-center justify-center p-4">
<div class="w-full max-w-sm bg-white rounded-xl shadow-lg p-8">
<h1 class="text-3xl font-bold text-center text-gray-800 mb-8">
Registrácia
</h1>
<form id="registerForm" action="/register" method="POST" class="space-y-6">
<div>
<label for="meno" class="block text-sm font-medium text-gray-700 mb-1">
Meno / používateľ
</label>
<input
type="text"
id="meno"
name="username"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="Zadaj svoje meno"
/>
</div>
<div>
<label for="heslo" class="block text-sm font-medium text-gray-700 mb-1">
Heslo
</label>
<input
type="password"
id="heslo"
name="password"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="••••••••"
/>
</div>
<div>
<label for="heslo2" class="block text-sm font-medium text-gray-700 mb-1">
Heslo znova
</label>
<input
type="password"
id="heslo2"
name="password2"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="Potvrď heslo"
/>
</div>
<button
type="submit"
class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 rounded-lg transition duration-200"
>
Vytvoriť účet
</button>
</form>
<div class="text-center mt-6 text-sm text-gray-600">
Už máš účet?
<a href="/login.html" class="text-orange-600 hover:underline font-medium">
Prihlás sa
</a>
</div>
<p id="error" class="hidden mt-4 text-sm text-red-700 bg-red-50 border border-red-200 rounded-lg px-4 py-3 text-center"></p>
</div>
<script>
const params = new URLSearchParams(window.location.search);
const error = params.get('error');
const errorBox = document.getElementById('error');
if (error) {
errorBox.textContent = error;
errorBox.classList.remove('hidden');
}
</script>
</body>
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Registrácia</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-gray-100 flex items-center justify-center p-4">
<div class="w-full max-w-sm bg-white rounded-xl shadow-lg p-8">
<h1 class="text-3xl font-bold text-center text-gray-800 mb-8">
Registrácia
</h1>
<form id="registerForm" action="/register" method="POST" class="space-y-6">
<div>
<label for="meno" class="block text-sm font-medium text-gray-700 mb-1">
Meno / používateľ
</label>
<input
type="text"
id="meno"
name="username"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="Zadaj svoje meno"
/>
</div>
<div>
<label for="heslo" class="block text-sm font-medium text-gray-700 mb-1">
Heslo
</label>
<input
type="password"
id="heslo"
name="password"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="••••••••"
/>
</div>
<div>
<label for="heslo2" class="block text-sm font-medium text-gray-700 mb-1">
Heslo znova
</label>
<input
type="password"
id="heslo2"
name="password2"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-orange-400"
placeholder="Potvrď heslo"
/>
</div>
<button
type="submit"
class="w-full bg-orange-500 hover:bg-orange-600 text-white font-medium py-3 rounded-lg transition duration-200"
>
Vytvoriť účet
</button>
</form>
<div class="text-center mt-6 text-sm text-gray-600">
Už máš účet?
<a href="/login.html" class="text-orange-600 hover:underline font-medium">
Prihlás sa
</a>
</div>
<p id="error" class="hidden mt-4 text-sm text-red-700 bg-red-50 border border-red-200 rounded-lg px-4 py-3 text-center"></p>
</div>
<script>
const params = new URLSearchParams(window.location.search);
const error = params.get('error');
const errorBox = document.getElementById('error');
if (error) {
errorBox.textContent = error;
errorBox.classList.remove('hidden');
}
</script>
</body>
</html>

4296
z1/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
{
"dependencies": {
"bcryptjs": "^3.0.3",
"body-parser": "^2.2.2",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"express-session": "^1.19.0",
"mysql2": "^3.20.0",
"sqlite3": "^6.0.1"
}
}
{
"dependencies": {
"bcryptjs": "^3.0.3",
"body-parser": "^2.2.2",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"express-session": "^1.19.0",
"mysql2": "^3.20.0",
"sqlite3": "^6.0.1"
}
}