2025-05-25 16:25:22 +02:00
|
|
|
use actix_web::{Error, post, get, web, App, HttpResponse, HttpServer, Responder, HttpRequest};
|
2025-05-23 15:25:48 +02:00
|
|
|
use actix_cors::Cors;
|
|
|
|
use actix_files::Files;
|
|
|
|
use dotenv::dotenv;
|
|
|
|
use env_logger::{Builder, Env};
|
|
|
|
use sqlx::postgres::PgPoolOptions;
|
2025-05-25 16:25:22 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2025-05-23 15:25:48 +02:00
|
|
|
use serde_json::json;
|
2025-05-24 16:47:32 +02:00
|
|
|
use std::collections::HashMap;
|
2025-05-25 16:25:22 +02:00
|
|
|
use bigdecimal::BigDecimal;
|
|
|
|
use chrono::{TimeZone, DateTime, Utc, NaiveDateTime};
|
|
|
|
use sqlx::FromRow;
|
|
|
|
use actix_web::http::header;
|
|
|
|
use sqlx::Row;
|
|
|
|
use bigdecimal::FromPrimitive;
|
2025-05-25 16:54:16 +02:00
|
|
|
use std::convert::Infallible;
|
2025-05-25 16:25:22 +02:00
|
|
|
|
|
|
|
mod auth;
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct RegistrationData {
|
|
|
|
email: String,
|
|
|
|
haslo: String,
|
|
|
|
imie: String,
|
|
|
|
#[serde(rename = "confirmPassword")]
|
|
|
|
confirm_password: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct LoginData {
|
|
|
|
email: String,
|
|
|
|
haslo: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
|
struct LoginResponse {
|
|
|
|
token: String,
|
|
|
|
imie: String,
|
|
|
|
}
|
2025-05-23 15:25:48 +02:00
|
|
|
|
|
|
|
#[derive(sqlx::FromRow, serde::Serialize)]
|
|
|
|
struct Book {
|
|
|
|
id: i32,
|
|
|
|
tytul: String,
|
|
|
|
autor: String,
|
|
|
|
cena: BigDecimal,
|
2025-05-24 16:47:32 +02:00
|
|
|
obraz_url: Option<String>,
|
|
|
|
opis: Option<String>,
|
2025-05-23 15:25:48 +02:00
|
|
|
}
|
|
|
|
|
2025-05-25 16:25:22 +02:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct CartItem {
|
|
|
|
book_id: i32,
|
|
|
|
quantity: i32,
|
|
|
|
}
|
|
|
|
|
2025-05-25 17:52:16 +02:00
|
|
|
#[derive(Serialize)]
|
|
|
|
struct OrderWithItems {
|
2025-05-25 16:25:22 +02:00
|
|
|
id: i32,
|
2025-05-25 17:52:16 +02:00
|
|
|
data_zamowienia: NaiveDateTime,
|
2025-05-25 16:25:22 +02:00
|
|
|
suma_totalna: BigDecimal,
|
|
|
|
status: Option<String>,
|
2025-05-25 17:52:16 +02:00
|
|
|
items: Vec<OrderItem>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(sqlx::FromRow, Serialize)]
|
|
|
|
struct OrderItem {
|
|
|
|
tytul: String,
|
|
|
|
autor: String,
|
|
|
|
ilosc: i32,
|
|
|
|
cena: BigDecimal,
|
2025-05-25 16:25:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct CheckoutRequest {
|
|
|
|
items: Vec<CartItem>,
|
|
|
|
total: f64,
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn validate_token(token: Option<&str>) -> Result<i32, actix_web::Error> {
|
|
|
|
let raw_token = token.ok_or(actix_web::error::ErrorUnauthorized("Unauthorized"))?;
|
|
|
|
|
|
|
|
// Usuń prefiks "Bearer "
|
|
|
|
let token = raw_token.trim_start_matches("Bearer ").trim();
|
|
|
|
|
|
|
|
if token.starts_with("user-") {
|
|
|
|
let user_id = token.replace("user-", "").replace("-token", "").parse()
|
|
|
|
.map_err(|_| actix_web::error::ErrorUnauthorized("Invalid token"))?;
|
|
|
|
Ok(user_id)
|
|
|
|
} else {
|
|
|
|
Err(actix_web::error::ErrorUnauthorized("Unauthorized"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 bcrypt::hash(&form.haslo, bcrypt::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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 bcrypt::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"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-23 15:25:48 +02:00
|
|
|
#[get("/api/ksiazki")]
|
2025-05-24 16:47:32 +02:00
|
|
|
async fn get_ksiazki(
|
|
|
|
pool: web::Data<sqlx::PgPool>,
|
|
|
|
web::Query(params): web::Query<HashMap<String, String>>,
|
|
|
|
) -> impl Responder {
|
|
|
|
let search_term = params.get("search").map(|s| s.as_str()).unwrap_or("");
|
|
|
|
let sort_by = params.get("sort").map(|s| s.as_str()).unwrap_or("default");
|
2025-05-25 16:25:22 +02:00
|
|
|
|
2025-05-26 18:48:23 +02:00
|
|
|
// Poprawione zapytanie bazowe
|
|
|
|
let mut base_query = "SELECT
|
2025-05-24 16:47:32 +02:00
|
|
|
id,
|
|
|
|
tytul,
|
|
|
|
autor,
|
|
|
|
cena,
|
|
|
|
COALESCE('/images/' || obraz_url, '/images/placeholder.jpg') as obraz_url,
|
2025-05-25 16:25:22 +02:00
|
|
|
COALESCE(opis, 'Brak opisu') as opis
|
2025-05-26 18:48:23 +02:00
|
|
|
FROM ksiazki".to_string();
|
2025-05-24 16:47:32 +02:00
|
|
|
|
2025-05-26 18:48:23 +02:00
|
|
|
// Warunek WHERE
|
|
|
|
let where_clause = if !search_term.is_empty() {
|
|
|
|
" WHERE LOWER(tytul) LIKE LOWER($1) OR LOWER(autor) LIKE LOWER($1)"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
};
|
|
|
|
|
|
|
|
// Poprawna kolejność klauzul
|
|
|
|
let order_clause = match sort_by {
|
2025-05-24 16:47:32 +02:00
|
|
|
"price_asc" => " ORDER BY cena ASC",
|
|
|
|
"price_desc" => " ORDER BY cena DESC",
|
|
|
|
"title_asc" => " ORDER BY tytul ASC",
|
|
|
|
"author_asc" => " ORDER BY autor ASC",
|
2025-05-26 18:48:23 +02:00
|
|
|
_ => " ORDER BY tytul ASC" // Domyślne sortowanie
|
2025-05-24 16:47:32 +02:00
|
|
|
};
|
|
|
|
|
2025-05-26 18:48:23 +02:00
|
|
|
// Łączymy części zapytania w odpowiedniej kolejności
|
|
|
|
let query = format!("{}{}{}", base_query, where_clause, order_clause);
|
2025-05-24 16:47:32 +02:00
|
|
|
|
|
|
|
let mut query_builder = sqlx::query_as::<_, Book>(&query);
|
2025-05-25 16:25:22 +02:00
|
|
|
|
2025-05-24 16:47:32 +02:00
|
|
|
if !search_term.is_empty() {
|
|
|
|
query_builder = query_builder.bind(format!("%{}%", search_term));
|
|
|
|
}
|
|
|
|
|
|
|
|
match query_builder.fetch_all(pool.get_ref()).await {
|
|
|
|
Ok(books) => HttpResponse::Ok().json(books),
|
|
|
|
Err(e) => {
|
|
|
|
log::error!("Błąd bazy danych: {:?}", e);
|
|
|
|
HttpResponse::InternalServerError().json(json!({"error": "Błąd serwera"}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/api/ksiazki/{id}")]
|
|
|
|
async fn get_ksiazka(
|
|
|
|
pool: web::Data<sqlx::PgPool>,
|
|
|
|
path: web::Path<i32>,
|
|
|
|
) -> impl Responder {
|
|
|
|
let id = path.into_inner();
|
2025-05-25 16:25:22 +02:00
|
|
|
|
2025-05-23 15:25:48 +02:00
|
|
|
match sqlx::query_as!(
|
|
|
|
Book,
|
2025-05-24 16:47:32 +02:00
|
|
|
r#"
|
2025-05-25 16:25:22 +02:00
|
|
|
SELECT
|
2025-05-24 16:47:32 +02:00
|
|
|
id,
|
|
|
|
tytul,
|
|
|
|
autor,
|
|
|
|
cena,
|
|
|
|
COALESCE('/images/' || obraz_url, '/images/placeholder.jpg') as obraz_url,
|
2025-05-24 19:23:52 +02:00
|
|
|
COALESCE(opis, 'Brak opisu') as opis
|
2025-05-25 16:25:22 +02:00
|
|
|
FROM ksiazki
|
2025-05-24 16:47:32 +02:00
|
|
|
WHERE id = $1
|
|
|
|
"#,
|
|
|
|
id
|
2025-05-23 15:25:48 +02:00
|
|
|
)
|
2025-05-24 16:47:32 +02:00
|
|
|
.fetch_optional(pool.get_ref())
|
2025-05-23 15:25:48 +02:00
|
|
|
.await
|
|
|
|
{
|
2025-05-24 16:47:32 +02:00
|
|
|
Ok(Some(book)) => HttpResponse::Ok().json(book),
|
2025-05-24 19:23:52 +02:00
|
|
|
Ok(None) => HttpResponse::NotFound().json(json!({"error": "Książka nie znaleziona"})),
|
|
|
|
Err(e) => {
|
|
|
|
log::error!("Błąd bazy danych: {}", e);
|
|
|
|
HttpResponse::InternalServerError().json(json!({"error": "Błąd serwera"}))
|
|
|
|
}
|
2025-05-23 15:25:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-26 19:24:45 +02:00
|
|
|
#[derive(Serialize)]
|
|
|
|
struct UserInfo {
|
|
|
|
id: i32,
|
|
|
|
imie: String,
|
|
|
|
}
|
|
|
|
|
2025-05-23 15:25:48 +02:00
|
|
|
#[get("/api/check-auth")]
|
2025-05-26 19:24:45 +02:00
|
|
|
async fn check_auth(
|
|
|
|
req: HttpRequest,
|
|
|
|
pool: web::Data<sqlx::PgPool>, // Dodajemy pool jako parametr
|
|
|
|
) -> impl Responder {
|
2025-05-25 16:25:22 +02:00
|
|
|
let token = req.headers().get("Authorization")
|
|
|
|
.and_then(|h| h.to_str().ok());
|
|
|
|
|
|
|
|
match validate_token(token).await {
|
2025-05-26 19:24:45 +02:00
|
|
|
Ok(user_id) => {
|
|
|
|
match sqlx::query!(
|
|
|
|
"SELECT imie FROM uzytkownicy WHERE id = $1",
|
|
|
|
user_id
|
|
|
|
)
|
|
|
|
.fetch_one(pool.get_ref()) // Używamy pool z parametru
|
|
|
|
.await {
|
|
|
|
Ok(u) => HttpResponse::Ok().json(json!({
|
|
|
|
"authenticated": true,
|
|
|
|
"user": {
|
|
|
|
"id": user_id,
|
|
|
|
"imie": u.imie
|
|
|
|
}
|
|
|
|
})),
|
|
|
|
Err(_) => HttpResponse::Ok().json(json!({
|
|
|
|
"authenticated": false,
|
|
|
|
"user": null
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
},
|
2025-05-25 16:25:22 +02:00
|
|
|
Err(_) => HttpResponse::Ok().json(json!({
|
|
|
|
"authenticated": false,
|
|
|
|
"user": null
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(serde::Serialize)]
|
|
|
|
struct CartItemResponse {
|
|
|
|
book_id: i32,
|
|
|
|
quantity: i32,
|
|
|
|
tytul: String,
|
|
|
|
cena: BigDecimal,
|
|
|
|
#[serde(rename = "obraz_url")]
|
|
|
|
obraz_url: String, // Zmiana z Option<String> na String
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/api/cart")]
|
|
|
|
async fn get_cart(
|
|
|
|
req: HttpRequest,
|
|
|
|
pool: web::Data<sqlx::PgPool>,
|
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
let user_id = validate_token(get_token(&req)).await?;
|
|
|
|
|
|
|
|
let cart_items = sqlx::query_as!(
|
|
|
|
CartItemResponse,
|
|
|
|
r#"SELECT
|
|
|
|
k.book_id as "book_id!",
|
|
|
|
k.quantity as "quantity!",
|
|
|
|
b.tytul as "tytul!",
|
|
|
|
b.cena as "cena!",
|
|
|
|
COALESCE('/images/' || NULLIF(b.obraz_url, ''), '/images/placeholder.jpg') as "obraz_url!"
|
|
|
|
FROM koszyk k
|
|
|
|
JOIN ksiazki b ON k.book_id = b.id
|
2025-05-26 17:36:01 +02:00
|
|
|
WHERE k.user_id = $1
|
|
|
|
ORDER BY b.tytul"#,
|
2025-05-25 16:25:22 +02:00
|
|
|
user_id
|
|
|
|
)
|
|
|
|
.fetch_all(pool.get_ref())
|
|
|
|
.await
|
|
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
|
|
|
|
|
|
|
Ok(HttpResponse::Ok().json(cart_items))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_token(req: &HttpRequest) -> Option<&str> {
|
|
|
|
req.headers()
|
|
|
|
.get("Authorization")
|
|
|
|
.and_then(|h| h.to_str().ok())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/api/add-to-cart")]
|
|
|
|
async fn add_to_cart(
|
|
|
|
cart_item: web::Json<CartItem>,
|
|
|
|
req: HttpRequest,
|
|
|
|
pool: web::Data<sqlx::PgPool>,
|
|
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
|
|
|
let token = req.headers().get("Authorization")
|
|
|
|
.and_then(|h| h.to_str().ok());
|
|
|
|
|
|
|
|
let user_id = validate_token(token).await?;
|
|
|
|
|
|
|
|
sqlx::query!(
|
|
|
|
"INSERT INTO koszyk (user_id, book_id, quantity)
|
|
|
|
VALUES ($1, $2, $3)
|
|
|
|
ON CONFLICT (user_id, book_id)
|
|
|
|
DO UPDATE SET quantity = koszyk.quantity + $3",
|
|
|
|
user_id,
|
|
|
|
cart_item.book_id,
|
|
|
|
cart_item.quantity
|
|
|
|
)
|
|
|
|
.execute(pool.get_ref())
|
|
|
|
.await
|
|
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
|
|
|
|
|
|
|
Ok(HttpResponse::Ok().json(json!({"status": "success"})))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/api/order-history")]
|
|
|
|
async fn get_order_history(
|
|
|
|
req: HttpRequest,
|
|
|
|
pool: web::Data<sqlx::PgPool>,
|
|
|
|
) -> Result<HttpResponse, Error> {
|
2025-05-25 17:52:16 +02:00
|
|
|
let user_id = validate_token(get_token(&req)).await?;
|
2025-05-25 16:25:22 +02:00
|
|
|
|
2025-05-25 17:52:16 +02:00
|
|
|
let orders = sqlx::query!(
|
|
|
|
r#"
|
|
|
|
SELECT
|
|
|
|
z.id as "id!",
|
|
|
|
z.data_zamowienia as "data_zamowienia!",
|
|
|
|
z.suma_totalna as "suma_totalna!",
|
|
|
|
z.status,
|
|
|
|
pz.ilosc as "ilosc!",
|
|
|
|
pz.cena as "item_price!",
|
|
|
|
k.tytul as "tytul!",
|
|
|
|
k.autor as "autor!"
|
|
|
|
FROM zamowienia z
|
|
|
|
JOIN pozycje_zamowienia pz ON z.id = pz.zamowienie_id
|
|
|
|
JOIN ksiazki k ON pz.book_id = k.id
|
|
|
|
WHERE z.user_id = $1
|
2025-05-26 17:36:01 +02:00
|
|
|
ORDER BY z.id DESC, k.tytul ASC
|
2025-05-25 17:52:16 +02:00
|
|
|
"#,
|
|
|
|
user_id
|
|
|
|
)
|
2025-05-25 16:25:22 +02:00
|
|
|
.fetch_all(pool.get_ref())
|
|
|
|
.await
|
|
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
|
|
|
|
2025-05-25 17:52:16 +02:00
|
|
|
let mut grouped_orders = HashMap::new();
|
|
|
|
for record in orders {
|
|
|
|
let entry = grouped_orders.entry(record.id).or_insert(OrderWithItems {
|
|
|
|
id: record.id,
|
|
|
|
data_zamowienia: record.data_zamowienia,
|
|
|
|
suma_totalna: record.suma_totalna,
|
|
|
|
status: record.status,
|
|
|
|
items: Vec::new(),
|
|
|
|
});
|
|
|
|
|
|
|
|
entry.items.push(OrderItem {
|
|
|
|
tytul: record.tytul,
|
|
|
|
autor: record.autor,
|
|
|
|
ilosc: record.ilosc,
|
|
|
|
cena: record.item_price,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
let result: Vec<OrderWithItems> = grouped_orders.into_values().collect();
|
|
|
|
Ok(HttpResponse::Ok().json(result))
|
2025-05-25 16:25:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/api/checkout")]
|
|
|
|
async fn checkout(
|
|
|
|
req: HttpRequest,
|
|
|
|
pool: web::Data<sqlx::PgPool>,
|
|
|
|
data: web::Json<CheckoutRequest>,
|
|
|
|
) -> Result<HttpResponse, actix_web::Error> {
|
2025-05-25 16:54:16 +02:00
|
|
|
let user_id = validate_token(get_token(&req)).await?;
|
2025-05-25 16:25:22 +02:00
|
|
|
|
2025-05-25 16:54:16 +02:00
|
|
|
let mut transaction = pool.begin().await
|
|
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
2025-05-25 16:25:22 +02:00
|
|
|
|
2025-05-25 16:54:16 +02:00
|
|
|
// 1. Utwórz zamówienie
|
2025-05-25 16:25:22 +02:00
|
|
|
let order_id = sqlx::query!(
|
|
|
|
"INSERT INTO zamowienia (user_id, suma_totalna)
|
|
|
|
VALUES ($1, $2) RETURNING id",
|
|
|
|
user_id,
|
2025-05-25 16:54:16 +02:00
|
|
|
BigDecimal::from_f64(data.total).ok_or_else(||
|
|
|
|
actix_web::error::ErrorBadRequest("Invalid total value"))?
|
2025-05-25 16:25:22 +02:00
|
|
|
)
|
|
|
|
.fetch_one(&mut *transaction)
|
|
|
|
.await
|
|
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?
|
|
|
|
.id;
|
|
|
|
|
2025-05-25 16:54:16 +02:00
|
|
|
// 2. Dodaj pozycje zamówienia
|
2025-05-25 16:25:22 +02:00
|
|
|
for item in &data.items {
|
|
|
|
let book = sqlx::query!(
|
|
|
|
"SELECT cena FROM ksiazki WHERE id = $1",
|
|
|
|
item.book_id
|
|
|
|
)
|
|
|
|
.fetch_one(&mut *transaction)
|
|
|
|
.await
|
|
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
|
|
|
|
|
|
|
sqlx::query!(
|
|
|
|
"INSERT INTO pozycje_zamowienia (zamowienie_id, book_id, ilosc, cena)
|
|
|
|
VALUES ($1, $2, $3, $4)",
|
|
|
|
order_id,
|
|
|
|
item.book_id,
|
|
|
|
item.quantity,
|
|
|
|
book.cena
|
|
|
|
)
|
|
|
|
.execute(&mut *transaction)
|
|
|
|
.await
|
|
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
|
|
|
}
|
|
|
|
|
2025-05-25 16:54:16 +02:00
|
|
|
// 3. Wyczyść koszyk
|
|
|
|
sqlx::query!(
|
|
|
|
"DELETE FROM koszyk WHERE user_id = $1",
|
|
|
|
user_id
|
|
|
|
)
|
|
|
|
.execute(&mut *transaction)
|
|
|
|
.await
|
|
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
2025-05-25 16:25:22 +02:00
|
|
|
|
2025-05-25 16:54:16 +02:00
|
|
|
transaction.commit().await
|
|
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
|
|
|
|
|
|
|
Ok(HttpResponse::Ok().json(json!({"status": "success"})))
|
2025-05-23 15:25:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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"),
|
2025-05-25 16:25:22 +02:00
|
|
|
])
|
|
|
|
.supports_credentials();
|
2025-05-23 15:25:48 +02:00
|
|
|
|
|
|
|
App::new()
|
2025-05-25 16:25:22 +02:00
|
|
|
.app_data(web::Data::new(pool.clone()))
|
2025-05-23 15:25:48 +02:00
|
|
|
.wrap(cors)
|
|
|
|
.wrap(actix_web::middleware::Logger::default())
|
|
|
|
.service(get_ksiazki)
|
2025-05-24 19:23:52 +02:00
|
|
|
.service(get_ksiazka)
|
2025-05-25 16:25:22 +02:00
|
|
|
.service(rejestracja)
|
|
|
|
.service(login)
|
|
|
|
.service(get_cart)
|
|
|
|
.service(add_to_cart) // Dodaj
|
|
|
|
.service(checkout) // Dodaj
|
|
|
|
.service(check_auth)
|
|
|
|
.service(get_order_history)
|
2025-05-24 16:47:32 +02:00
|
|
|
.service(
|
2025-05-25 16:25:22 +02:00
|
|
|
Files::new("/images", "./static/images")
|
2025-05-24 16:47:32 +02:00
|
|
|
.show_files_listing(),
|
|
|
|
)
|
2025-05-23 15:25:48 +02:00
|
|
|
.service(Files::new("/", "./static").index_file("index.html"))
|
|
|
|
})
|
|
|
|
.bind("0.0.0.0:7999")?
|
|
|
|
.run()
|
|
|
|
.await
|
|
|
|
}
|
2025-05-25 16:25:22 +02:00
|
|
|
|