init repo
1
.env
Normal file
|
@ -0,0 +1 @@
|
|||
DATABASE_URL="postgres://postgres:secret@localhost/ksiegarnia"
|
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
3379
Cargo.lock
generated
Normal file
19
Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "ksiegarnia"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.4.0"
|
||||
actix-cors = "0.7.0"
|
||||
actix-files = "0.6.2"
|
||||
env_logger = "0.11"
|
||||
dotenv = "0.15"
|
||||
sqlx = { version = "0.7.4", features = ["postgres", "runtime-tokio-native-tls", "macros", "chrono", "bigdecimal"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||
log = "0.4"
|
||||
bcrypt = "0.15"
|
||||
rust_decimal = { version = "1.37.1", features = ["serde", "db-postgres"] }
|
||||
bigdecimal = { version = "0.3.0", features = ["serde"] }
|
17
migrations/20250519_init.sql
Normal file
|
@ -0,0 +1,17 @@
|
|||
-- Tabela użytkowników
|
||||
CREATE TABLE uzytkownicy (
|
||||
id SERIAL PRIMARY KEY,
|
||||
imie VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
haslo VARCHAR(255) NOT NULL,
|
||||
data_rejestracji TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabela książek
|
||||
CREATE TABLE ksiazki (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tytul VARCHAR(255) NOT NULL,
|
||||
autor VARCHAR(255) NOT NULL,
|
||||
cena DECIMAL(10, 2) NOT NULL
|
||||
);
|
||||
|
22
migrations/20250522_books.sql
Normal file
|
@ -0,0 +1,22 @@
|
|||
ALTER TABLE ksiazki
|
||||
ADD COLUMN obraz_url VARCHAR(255);
|
||||
|
||||
INSERT INTO ksiazki (tytul, autor, cena, obraz_url) VALUES
|
||||
('Descent of Angels', 'Mitchel Scanlon', 49.99, 'descent-of-angels.jpg'),
|
||||
('Horus Rising', 'Dan Abnett', 49.99, 'horus-rising.jpg'),
|
||||
('False Gods', 'Graham McNeill', 49.99, 'false-gods.jpg'),
|
||||
('Galaxy in Flames', 'Ben Counter', 49.99, 'galaxy-in-flames.jpg'),
|
||||
('Fulgrim', 'Graham McNeill', 49.99, 'fulgrim.jpg'),
|
||||
('Flight of the Eisenstein', 'James Swallow', 49.99, 'flight-of-the-eisenstein.jpg'),
|
||||
('Echoes of Eternity', 'Aaron Dembski-Bowden', 59.99, 'echoes-of-eternity.jpg'),
|
||||
('First and Only', 'Dan Abnett', 49.99, 'first-and-only.jpg'),
|
||||
('The Devastation of Baal', 'Guy Haley', 59.99, 'devastation-of-baal.jpg'),
|
||||
('Fabius Bile: Primogenitor', 'Joshua Reynolds', 39.99, 'fabius-bile-primogenitor.jpg'),
|
||||
('Fabius Bile: Clonelord', 'Joshua Reynolds', 39.99, 'fabius-bile-clonelord.jpg'),
|
||||
('Fabius Bile: Manflayer', 'Joshua Reynolds', 39.99, 'fabius-bile-manflayer.jpg'),
|
||||
('Ahriman: Exile', 'John French', 39.99, 'ahriman-exile.jpg'),
|
||||
('Ahriman: Sorcerer', 'John French', 39.99, 'ahriman-sorcerer.jpg'),
|
||||
('Ahriman: Unchanged', 'John French', 39.99, 'ahriman-unchanged.jpg'),
|
||||
('Mephiston: Blood of Sanguinius', 'Darius Hinks', 39.99, 'mephiston-blood-of-sanguinius.jpg'),
|
||||
('Mephiston: Revenant Crusade', 'Darius Hinks', 39.99, 'mephiston-revenant-crusade.jpg'),
|
||||
('Mephiston: City of Light', 'Darius Hinks', 39.99, 'mephiston-city-of-light.jpg');
|
2
migrations/bigdecimal.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE ksiazki
|
||||
ALTER COLUMN cena TYPE NUMERIC(10,2);
|
12
schema.sql
Normal file
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE ksiazki (
|
||||
id SERIAL PRIMARY KEY,
|
||||
tytul VARCHAR(255) NOT NULL,
|
||||
autor VARCHAR(255) NOT NULL,
|
||||
cena DECIMAL(10, 2) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE uzytkownicy (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
haslo VARCHAR(255) NOT NULL
|
||||
);
|
100
src/auth.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
use actix_web::{post, web, HttpResponse, Responder};
|
||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RegistrationData {
|
||||
email: String,
|
||||
haslo: String,
|
||||
imie: String,
|
||||
#[serde(rename = "confirmPassword")]
|
||||
confirm_password: String,
|
||||
}
|
||||
|
||||
#[post("/rejestracja")]
|
||||
async fn rejestracja(
|
||||
form: web::Json<RegistrationData>,
|
||||
pool: web::Data<sqlx::PgPool>,
|
||||
) -> impl Responder {
|
||||
// Walidacja hasła
|
||||
if form.haslo.len() < 8 {
|
||||
return HttpResponse::BadRequest().body("Hasło musi mieć minimum 8 znaków");
|
||||
}
|
||||
|
||||
// Sprawdzenie, czy hasła się zgadzają
|
||||
if form.haslo != form.confirm_password {
|
||||
return HttpResponse::BadRequest().body("Hasła nie są identyczne");
|
||||
}
|
||||
|
||||
// Hashowanie hasła
|
||||
let hashed_password = match hash(&form.haslo, DEFAULT_COST) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return HttpResponse::InternalServerError().finish(),
|
||||
};
|
||||
|
||||
// Zapisz do bazy danych
|
||||
match sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO uzytkownicy (email, haslo, imie)
|
||||
VALUES ($1, $2, $3)
|
||||
"#,
|
||||
form.email,
|
||||
hashed_password,
|
||||
form.imie
|
||||
)
|
||||
.execute(pool.get_ref())
|
||||
.await
|
||||
{
|
||||
Ok(_) => HttpResponse::Created().body("Konto utworzone pomyślnie"),
|
||||
Err(e) => {
|
||||
if e.to_string().contains("duplicate key value") {
|
||||
HttpResponse::Conflict().body("Email jest już zarejestrowany")
|
||||
} else {
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LoginData {
|
||||
email: String,
|
||||
haslo: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LoginResponse {
|
||||
token: String,
|
||||
imie: String,
|
||||
}
|
||||
|
||||
#[post("/login")]
|
||||
async fn login(
|
||||
form: web::Json<LoginData>,
|
||||
pool: web::Data<sqlx::PgPool>,
|
||||
) -> impl Responder {
|
||||
let user = match sqlx::query!(
|
||||
"SELECT id, haslo, imie FROM uzytkownicy WHERE email = $1",
|
||||
form.email
|
||||
)
|
||||
.fetch_optional(pool.get_ref())
|
||||
.await
|
||||
{
|
||||
Ok(Some(u)) => u,
|
||||
Ok(None) => return HttpResponse::Unauthorized().body("Nieprawidłowe dane"),
|
||||
Err(_) => return HttpResponse::InternalServerError().finish(),
|
||||
};
|
||||
|
||||
match verify(&form.haslo, &user.haslo) {
|
||||
Ok(true) => {
|
||||
// W praktyce użyj JWT lub innego mechanizmu autentykacji
|
||||
let dummy_token = format!("user-{}-token", user.id);
|
||||
HttpResponse::Ok().json(LoginResponse {
|
||||
token: dummy_token,
|
||||
imie: user.imie,
|
||||
})
|
||||
}
|
||||
_ => HttpResponse::Unauthorized().body("Nieprawidłowe hasło"),
|
||||
}
|
||||
}
|
||||
|
89
src/main.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
mod auth;
|
||||
|
||||
use actix_cors::Cors;
|
||||
use actix_files::Files;
|
||||
use actix_web::{
|
||||
get, http::header, web, App, HttpResponse, HttpServer, Responder,
|
||||
};
|
||||
use dotenv::dotenv;
|
||||
use env_logger::{Builder, Env};
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use rust_decimal::Decimal;
|
||||
use bigdecimal::BigDecimal;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(sqlx::FromRow, serde::Serialize)]
|
||||
struct Book {
|
||||
id: i32,
|
||||
tytul: String,
|
||||
autor: String,
|
||||
cena: BigDecimal,
|
||||
obraz_url: Option<String>
|
||||
}
|
||||
|
||||
#[get("/api/ksiazki")]
|
||||
async fn get_ksiazki(pool: web::Data<sqlx::PgPool>) -> impl Responder {
|
||||
match sqlx::query_as!(
|
||||
Book,
|
||||
r#"SELECT id, tytul, autor, cena, obraz_url FROM ksiazki"#
|
||||
)
|
||||
.fetch_all(pool.get_ref())
|
||||
.await
|
||||
{
|
||||
Ok(books) => HttpResponse::Ok().json(books),
|
||||
Err(e) => {
|
||||
log::error!("Błąd bazy danych: {:?}", e);
|
||||
HttpResponse::InternalServerError().body(format!("Błąd: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/check-auth")]
|
||||
async fn check_auth() -> impl Responder {
|
||||
HttpResponse::Ok().json(json!({
|
||||
"authenticated": false,
|
||||
"user": null
|
||||
}))
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
// Inicjalizacja loggera
|
||||
Builder::from_env(Env::default().default_filter_or("debug")).init();
|
||||
|
||||
// Ładowanie zmiennych środowiskowych
|
||||
dotenv().ok();
|
||||
let database_url = std::env::var("DATABASE_URL")
|
||||
.expect("DATABASE_URL must be set in .env");
|
||||
|
||||
// Utwórz pulę połączeń
|
||||
let pool = PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&database_url)
|
||||
.await
|
||||
.expect("Failed to create pool");
|
||||
|
||||
HttpServer::new(move || {
|
||||
let cors = Cors::default()
|
||||
.allow_any_origin()
|
||||
.allowed_methods(vec!["GET", "POST"])
|
||||
.allowed_headers(vec![
|
||||
header::CONTENT_TYPE,
|
||||
header::AUTHORIZATION,
|
||||
header::ACCEPT,
|
||||
header::HeaderName::from_static("content-type"),
|
||||
]);
|
||||
|
||||
App::new()
|
||||
.app_data(web::Data::new(pool.clone())) // Dodaj pulę jako globalny stan
|
||||
.wrap(cors)
|
||||
.wrap(actix_web::middleware::Logger::default())
|
||||
.service(get_ksiazki)
|
||||
.service(auth::rejestracja)
|
||||
.service(auth::login)
|
||||
.service(Files::new("/", "./static").index_file("index.html"))
|
||||
})
|
||||
.bind("0.0.0.0:7999")?
|
||||
.run()
|
||||
.await
|
||||
}
|
66
static/css/styles.css
Normal file
|
@ -0,0 +1,66 @@
|
|||
:root {
|
||||
--bg-dark: #072C24;
|
||||
--text-gold: #E3CB9A;
|
||||
--accent-blue: #78DBFF;
|
||||
--footer-text: #93B8B1;
|
||||
}
|
||||
|
||||
.dark-theme {
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--text-gold);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-family: 'Lobster', cursive;
|
||||
font-size: 2.2rem;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.auth-container a {
|
||||
color: var(--accent-blue) !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #09342b;
|
||||
padding: 2rem 0;
|
||||
margin-top: 4rem;
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: var(--footer-text);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(145deg, #08352c 0%, #062a23 100%);
|
||||
border: 1px solid #1c4d42;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-gothic {
|
||||
background-color: #1c4d42;
|
||||
color: var(--text-gold);
|
||||
border: 1px solid #2a6b5e;
|
||||
}
|
||||
|
||||
.btn-gothic:hover {
|
||||
background-color: #2a6b5e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
background: rgba(7, 44, 36, 0.9);
|
||||
border: 2px solid #2a6b5e;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.auth-container h2 {
|
||||
font-family: 'Lobster', cursive;
|
||||
text-align: center;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
BIN
static/images/ahriman-exile.jpg
Normal file
After Width: | Height: | Size: 145 KiB |
BIN
static/images/ahriman-sorcerer.jpg
Normal file
After Width: | Height: | Size: 138 KiB |
BIN
static/images/ahriman-unchanged.jpg
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
static/images/descent-of-angels.jpg
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
static/images/devastation-of-baal.jpg
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
static/images/echoes-of-eternity.jpg
Normal file
After Width: | Height: | Size: 198 KiB |
BIN
static/images/fabius-bile-clonelord.jpg
Normal file
After Width: | Height: | Size: 173 KiB |
BIN
static/images/fabius-bile-manflayer.jpg
Normal file
After Width: | Height: | Size: 203 KiB |
BIN
static/images/fabius-bile-primogenitor.jpg
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
static/images/false-gods.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
static/images/first-and-only.jpg
Normal file
After Width: | Height: | Size: 208 KiB |
BIN
static/images/flight-of-the-eisenstein.jpg
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
static/images/fulgrim.jpg
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
static/images/galaxy-in-flames.jpg
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
static/images/horus-rising.jpg
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
static/images/mephiston-blood-of-sanguinius.jpg
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
static/images/mephiston-city-of-light.jpg
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
static/images/mephiston-revenant-crusade.jpg
Normal file
After Width: | Height: | Size: 154 KiB |
49
static/index.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Dark Athenaeum</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/css/styles.css" rel="stylesheet">
|
||||
<link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
||||
<div class="navbar-nav">
|
||||
<!-- Dla niezalogowanych -->
|
||||
<div class="anonymous-links">
|
||||
<a class="nav-link" href="/login.html">Logowanie</a>
|
||||
<a class="nav-link" href="/register.html">Rejestracja</a>
|
||||
</div>
|
||||
|
||||
<!-- Dla zalogowanych -->
|
||||
<div class="user-links" style="display: none;">
|
||||
<a class="nav-link" href="/profile.html">Profil</a>
|
||||
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
||||
</div>
|
||||
|
||||
<a class="nav-link" href="/cart.html">
|
||||
<i class="bi bi-basket"></i> Koszyk
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<h2 class="text-center mb-5 neon-title">NOWOŚCI</h2>
|
||||
|
||||
<div class="row g-4" id="books-container">
|
||||
<!-- Dynamicznie ładowane książki -->
|
||||
<div class="col-12 text-center">
|
||||
<div class="spinner-border text-danger" role="status">
|
||||
<span class="visually-hidden">Ładowanie...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
135
static/js/main.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
function updateNavVisibility() {
|
||||
const token = localStorage.getItem('token');
|
||||
document.querySelectorAll('.anonymous-links, .user-links').forEach(el => {
|
||||
el.style.display = token ? 'none' : 'block';
|
||||
});
|
||||
document.querySelector('.user-links').style.display = token ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.getElementById('logoutLink')?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('imie');
|
||||
window.location.href = '/';
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
updateNavVisibility();
|
||||
|
||||
// Aktualizuj awatar/imię w nagłówku
|
||||
const userName = localStorage.getItem('imie');
|
||||
if(userName) {
|
||||
const profileLink = document.querySelector('a[href="/profile.html"]');
|
||||
if(profileLink) profileLink.innerHTML = `👤 ${userName}`;
|
||||
}
|
||||
});
|
||||
|
||||
async function loadUserData() {
|
||||
const response = await fetch('/api/check-auth');
|
||||
const data = await response.json();
|
||||
updateNavVisibility(data.authenticated);
|
||||
}
|
||||
|
||||
document.getElementById('loginForm')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const email = document.getElementById('loginEmail').value;
|
||||
const password = document.getElementById('loginPassword').value;
|
||||
|
||||
const response = await fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, haslo: password }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
localStorage.setItem('token', data.token);
|
||||
localStorage.setItem('imie', data.imie);
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
alert(data.message || 'Logowanie nieudane');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('registerForm')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const name = document.getElementById('registerName').value;
|
||||
const email = document.getElementById('registerEmail').value;
|
||||
const password = document.getElementById('registerPassword').value;
|
||||
const confirmPassword = document.getElementById('registerConfirmPassword').value;
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
alert('Hasła nie są identyczne');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('/rejestracja', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ imie: name, email, haslo: password, confirmPassword }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Rejestracja udana');
|
||||
window.location.href = '/login.html';
|
||||
} else {
|
||||
const data = await response.text();
|
||||
alert(data || 'Rejestracja nieudana');
|
||||
}
|
||||
});
|
||||
|
||||
async function loadBooks() {
|
||||
try {
|
||||
const response = await fetch('/api/ksiazki');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const books = await response.json();
|
||||
const booksContainer = document.getElementById('books-container');
|
||||
booksContainer.innerHTML = '';
|
||||
|
||||
books.forEach(book => {
|
||||
const bookElement = document.createElement('div');
|
||||
bookElement.className = 'col-md-4';
|
||||
bookElement.innerHTML = `
|
||||
<div class="card mb-4">
|
||||
<img src="${book.obraz_url || 'https://via.placeholder.com/150'}" class="card-img-top" alt="${book.tytul}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">${book.tytul}</h5>
|
||||
<p class="card-text">${book.autor}</p>
|
||||
<p class="card-text">${book.cena} PLN</p>
|
||||
<button class="btn btn-primary">Dodaj do koszyka</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
booksContainer.appendChild(bookElement);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error loading books:', error);
|
||||
const booksContainer = document.getElementById('books-container');
|
||||
booksContainer.innerHTML = '<div class="col-12 text-center"><p>Wystąpił błąd podczas ładowania książek.</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('books-container')) {
|
||||
loadBooks();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('books-container')) {
|
||||
loadBooks();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (document.getElementById('books-container')) {
|
||||
loadBooks();
|
||||
}
|
||||
});
|
||||
|
53
static/login.html
Normal file
|
@ -0,0 +1,53 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Logowanie</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/css/styles.css" rel="stylesheet">
|
||||
<link href="https://fonts.cdnfonts.com/css/old-english-text-mt" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
||||
<div class="navbar-nav">
|
||||
<!-- Dla niezalogowanych -->
|
||||
<div class="anonymous-links">
|
||||
<a class="nav-link" href="/login.html">Logowanie</a>
|
||||
<a class="nav-link" href="/register.html">Rejestracja</a>
|
||||
</div>
|
||||
|
||||
<!-- Dla zalogowanych -->
|
||||
<div class="user-links" style="display: none;">
|
||||
<a class="nav-link" href="/profile.html">Profil</a>
|
||||
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
||||
</div>
|
||||
|
||||
<a class="nav-link" href="/cart.html">
|
||||
<i class="bi bi-basket"></i> Koszyk
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="auth-container">
|
||||
<h2 class="neon-title mb-4">LOGOWANIE</h2>
|
||||
<form id="loginForm">
|
||||
<div class="mb-3">
|
||||
<input type="email" class="form-control dark-input"
|
||||
placeholder="Email" required id="loginEmail">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="password" class="form-control dark-input"
|
||||
placeholder="Hasło" required id="loginPassword">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-gothic w-100">ZALOGUJ SIĘ</button>
|
||||
</form>
|
||||
<div class="text-center mt-3">
|
||||
<a href="/register.html" class="text-danger">Nie masz konta? Zarejestruj się</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
57
static/register.html
Normal file
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Rejestracja</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/css/styles.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lobster&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-black">
|
||||
<div class="navbar-nav">
|
||||
<!-- Dla niezalogowanych -->
|
||||
<div class="anonymous-links">
|
||||
<a class="nav-link" href="/login.html">Logowanie</a>
|
||||
<a class="nav-link" href="/register.html">Rejestracja</a>
|
||||
</div>
|
||||
|
||||
<!-- Dla zalogowanych -->
|
||||
<div class="user-links" style="display: none;">
|
||||
<a class="nav-link" href="/profile.html">Profil</a>
|
||||
<a class="nav-link" href="#" id="logoutLink">Wyloguj</a>
|
||||
</div>
|
||||
|
||||
<a class="nav-link" href="/cart.html">
|
||||
<i class="bi bi-basket"></i> Koszyk
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="auth-container">
|
||||
<h2 class="neon-title mb-4">REJESTRACJA</h2>
|
||||
<form id="registerForm">
|
||||
<div class="mb-3">
|
||||
<input type="text" class="form-control dark-input"
|
||||
placeholder="Imię" required id="registerName">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="email" class="form-control dark-input"
|
||||
placeholder="Email" required id="registerEmail">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="password" class="form-control dark-input"
|
||||
placeholder="Hasło (min. 8 znaków)" required minlength="8" id="registerPassword">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="password" class="form-control dark-input"
|
||||
placeholder="Powtórz hasło" required id="registerConfirmPassword">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-gothic w-100">ZAREJESTRUJ SIĘ</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
142
struktura_bazy.sql
Normal file
|
@ -0,0 +1,142 @@
|
|||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 16.8
|
||||
-- Dumped by pg_dump version 16.8
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
SET idle_in_transaction_session_timeout = 0;
|
||||
SET client_encoding = 'UTF8';
|
||||
SET standard_conforming_strings = on;
|
||||
SELECT pg_catalog.set_config('search_path', '', false);
|
||||
SET check_function_bodies = false;
|
||||
SET xmloption = content;
|
||||
SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
|
||||
--
|
||||
-- Name: ksiazki; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.ksiazki (
|
||||
id integer NOT NULL,
|
||||
tytul character varying(255) NOT NULL,
|
||||
autor character varying(255) NOT NULL,
|
||||
cena numeric(10,2) NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.ksiazki OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: ksiazki_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.ksiazki_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.ksiazki_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: ksiazki_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.ksiazki_id_seq OWNED BY public.ksiazki.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: uzytkownicy; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.uzytkownicy (
|
||||
id integer NOT NULL,
|
||||
email character varying(255) NOT NULL,
|
||||
haslo character varying(255) NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.uzytkownicy OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: uzytkownicy_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.uzytkownicy_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER SEQUENCE public.uzytkownicy_id_seq OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: uzytkownicy_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.uzytkownicy_id_seq OWNED BY public.uzytkownicy.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: ksiazki id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.ksiazki ALTER COLUMN id SET DEFAULT nextval('public.ksiazki_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: uzytkownicy id; Type: DEFAULT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.uzytkownicy ALTER COLUMN id SET DEFAULT nextval('public.uzytkownicy_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ksiazki ksiazki_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.ksiazki
|
||||
ADD CONSTRAINT ksiazki_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: uzytkownicy uzytkownicy_email_key; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.uzytkownicy
|
||||
ADD CONSTRAINT uzytkownicy_email_key UNIQUE (email);
|
||||
|
||||
|
||||
--
|
||||
-- Name: uzytkownicy uzytkownicy_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.uzytkownicy
|
||||
ADD CONSTRAINT uzytkownicy_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: pg_database_owner
|
||||
--
|
||||
|
||||
GRANT USAGE ON SCHEMA public TO ksiegarnia_user;
|
||||
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
0
struktura_bazy.txt
Normal file
8
templates/dashboard.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head><meta charset="UTF-8"><title>Dashboard</title></head>
|
||||
<body>
|
||||
<h1>Witaj, użytkowniku {{ user_id }}!</h1>
|
||||
<a href="/logout">Wyloguj się</a>
|
||||
</body>
|
||||
</html>
|
13
templates/login.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head><meta charset="UTF-8"><title>Logowanie</title></head>
|
||||
<body>
|
||||
<h1>Logowanie</h1>
|
||||
<form action="/login" method="post">
|
||||
<label>Email:<input type="email" name="email" required></label><br>
|
||||
<label>Password:<input type="password" name="password" required></label><br>
|
||||
<button type="submit">Zaloguj się</button>
|
||||
</form>
|
||||
<a href="/register">Nie masz konta? Zarejestruj się</a>
|
||||
</body>
|
||||
</html>
|
14
templates/register.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head><meta charset="UTF-8"><title>Rejestracja</title></head>
|
||||
<body>
|
||||
<h1>Rejestracja</h1>
|
||||
<form action="/register" method="post">
|
||||
<label>Username:<input type="text" name="username" required></label><br>
|
||||
<label>Email:<input type="email" name="email" required></label><br>
|
||||
<label>Password:<input type="password" name="password" required></label><br>
|
||||
<button type="submit">Zarejestruj się</button>
|
||||
</form>
|
||||
<a href="/login">Mam już konto. Zaloguj się</a>
|
||||
</body>
|
||||
</html>
|