diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 21b9816..7bd5b4b 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1493,6 +1493,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bytes", + "chrono", "crc", "crossbeam-queue", "either", @@ -1569,6 +1570,7 @@ dependencies = [ "bitflags", "byteorder", "bytes", + "chrono", "crc", "digest", "dotenvy", @@ -1610,6 +1612,7 @@ dependencies = [ "base64", "bitflags", "byteorder", + "chrono", "crc", "dotenvy", "etcetera", @@ -1644,6 +1647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", + "chrono", "flume", "futures-channel", "futures-core", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 64426fd..d8a3fe1 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" axum = "0.8.9" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" -sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-native-tls"] } +sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-native-tls", "chrono"] } tokio = { version = "1.52.1", features = ["rt-multi-thread", "macros"] } dotenv = "0.15.0" chrono = { version = "0.4.44", features = ["serde"] } diff --git a/backend/src/handlers/mod.rs b/backend/src/handlers/mod.rs new file mode 100644 index 0000000..82dc4f1 --- /dev/null +++ b/backend/src/handlers/mod.rs @@ -0,0 +1 @@ +mod ticket; diff --git a/backend/src/handlers/ticket.rs b/backend/src/handlers/ticket.rs new file mode 100644 index 0000000..a8515a0 --- /dev/null +++ b/backend/src/handlers/ticket.rs @@ -0,0 +1,103 @@ +use std::sync::Arc; + +use axum::{ + Json, + extract::{Path, State}, + http::StatusCode, + response::IntoResponse, +}; +use serde_json::json; +use sqlx::query; + +use crate::{ + AppState, + models::{Ticket, TicketCreateScheme, TicketResponse}, +}; + +pub async fn create_ticket( + State(data): State>, + Json(body): Json, +) -> Result)> { + let query = query( + r#"INSERT INTO tickets (category, description, betreff, room) VALUES ($1, $2, $3, $4)"#, + ) + .bind(body.category.to_string()) + .bind(body.description.to_string()) + .bind(body.betreff.to_string()) + .bind(body.room.to_string()) + .execute(&data.db) + .await; + + if let Err(err) = query { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"status": "error", "message": format!("{:?}", err),})), + )); + } + + let response_status = serde_json::json!({"status": "success"}); + Ok(Json(response_status)) +} + +pub async fn delete_ticket( + Path(id): Path, + State(data): State>, +) -> Result)> { + let query = sqlx::query(r#"DELETE FROM tickets WHERE id = $1"#) + .bind(id) + .execute(&data.db) + .await + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"status": "error", "message": format!("{:?}", e)})), + ) + })?; + + if query.rows_affected() == 0 { + let error_response = serde_json::json!({ + "status": "error", + "message": format!("Ticket with ID {} not found", id) + }); + return Err((StatusCode::NOT_FOUND, Json(error_response))); + } + + Ok(StatusCode::NO_CONTENT) +} + +pub async fn get_tickets( + State(data): State>, +) -> Result)> { + let tickets = + sqlx::query_as(r#"SELECT * FROM tickets WHERE status <> 'Archived' ORDER BY date DESC"#) + .fetch_all(&data.db) + .await + .map_err(|e| { + let error_response = serde_json::json!({ + "status": "error", + "message": format!("Database error: {}", e), + }); + (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)) + })?; + + let ticket_response = tickets + .iter() + .map(|ticket| filter_record(&ticket)) + .collect::>(); + + let json_response = serde_json::json!(ticket_response); + Ok(Json(json_response)) +} + +fn filter_record(ticket: &Ticket) -> TicketResponse { + TicketResponse { + id: ticket.id.to_owned(), + category: ticket.category.to_owned(), + betreff: ticket.betreff.to_owned(), + description: ticket.description.to_owned(), + room: ticket.room.to_owned(), + status: ticket.status.to_owned(), + date: ticket.date.to_owned(), + user_id: ticket.user_id.to_owned(), + } +} diff --git a/backend/src/models.rs b/backend/src/models.rs index 0f446d0..d7f91e2 100644 --- a/backend/src/models.rs +++ b/backend/src/models.rs @@ -1,8 +1,9 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; +use sqlx::{Decode, prelude::Type}; -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Decode, Type)] pub enum Category { WhiteboardBeamer, Internet, @@ -25,7 +26,7 @@ impl Display for Category { } } -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Decode, Type)] pub enum Status { ToDo, InProgress, @@ -56,6 +57,18 @@ pub struct Ticket { pub user_id: i16, } +#[derive(Deserialize, Serialize, Debug, PartialEq)] +pub struct TicketResponse { + pub id: i32, + pub category: Category, + pub betreff: String, + pub description: String, + pub room: i16, + pub status: Status, + pub date: chrono::NaiveDateTime, + pub user_id: i16, +} + #[derive(Deserialize, Serialize, PartialEq, Debug)] pub struct User { pub id: i16, diff --git a/migrations/20260422094704_types.down.sql b/migrations/20260422094704_types.down.sql new file mode 100644 index 0000000..9fca44c --- /dev/null +++ b/migrations/20260422094704_types.down.sql @@ -0,0 +1,2 @@ +DROP TYPE category; +DROP TYPE status; diff --git a/migrations/20260422094704_types.up.sql b/migrations/20260422094704_types.up.sql new file mode 100644 index 0000000..4ed4011 --- /dev/null +++ b/migrations/20260422094704_types.up.sql @@ -0,0 +1,2 @@ +CREATE TYPE category AS ENUM('Whiteboard Beamer', 'Internet', 'iPad Koffer', 'Apple TV', 'Docu Cam', 'Sonstiges'); +CREATE TYPE status AS ENUM('ToDo', 'InProgress', 'Done', 'Archived'); diff --git a/migrations/20260422094706_ticket_table.down.sql b/migrations/20260422094706_ticket_table.down.sql new file mode 100644 index 0000000..a464732 --- /dev/null +++ b/migrations/20260422094706_ticket_table.down.sql @@ -0,0 +1,2 @@ +DROP TABLE tickets; + diff --git a/migrations/20260422094706_ticket_table.sql b/migrations/20260422094706_ticket_table.sql index ec765b1..c44c931 100644 --- a/migrations/20260422094706_ticket_table.sql +++ b/migrations/20260422094706_ticket_table.sql @@ -1,12 +1,10 @@ -CREATE TYPE category AS ENUM('Whiteboard Beamer', 'Internet', 'iPad Koffer', 'Apple TV', 'Docu Cam', 'Sonstiges') -CREAYE TYPE status AS ENUM('ToDo', 'InProgress', 'Done', 'Archived') CREATE TABLE IF NOT EXISTS tickets ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, category category NOT NULL DEFAULT 'Sonstiges', betreff VARCHAR(100), - description VARCHAR, + description TEXT, room SMALLINT, status status NOT NULL DEFAULT 'ToDo', - date TIMESTAMP, + date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, user_id SMALLINT ); diff --git a/migrations/20260422094717_user_table.sql b/migrations/20260422094717_user_table.sql index 6257e57..8682c4a 100644 --- a/migrations/20260422094717_user_table.sql +++ b/migrations/20260422094717_user_table.sql @@ -1,5 +1,5 @@ CREATE TABLE users ( - id SMALLINT PRIMARY KEY AUTOINCREMENT, + id SMALLINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR(30), firstname VARCHAR(30), is_admin BOOLEAN NOT NULL DEFAULT false