Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d890255631 | ||
|
|
20a0534c76 | ||
|
|
8b34dac813 | ||
|
|
1725d2538c | ||
|
|
a9e31e2fdf |
Generated
+4
@@ -1493,6 +1493,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"crossbeam-queue",
|
"crossbeam-queue",
|
||||||
"either",
|
"either",
|
||||||
@@ -1569,6 +1570,7 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"digest",
|
"digest",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
@@ -1610,6 +1612,7 @@ dependencies = [
|
|||||||
"base64",
|
"base64",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"etcetera",
|
"etcetera",
|
||||||
@@ -1644,6 +1647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
|
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
|
"chrono",
|
||||||
"flume",
|
"flume",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ edition = "2024"
|
|||||||
axum = "0.8.9"
|
axum = "0.8.9"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
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"] }
|
tokio = { version = "1.52.1", features = ["rt-multi-thread", "macros"] }
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
chrono = { version = "0.4.44", features = ["serde"] }
|
chrono = { version = "0.4.44", features = ["serde"] }
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod ticket;
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
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<Arc<AppState>>,
|
||||||
|
Json(body): Json<TicketCreateScheme>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
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<i32>,
|
||||||
|
State(data): State<Arc<AppState>>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
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<Arc<AppState>>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
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::<Vec<TicketResponse>>();
|
||||||
|
|
||||||
|
let json_response = serde_json::json!(ticket_response);
|
||||||
|
Ok(Json(json_response))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_ticket_by_id(
|
||||||
|
Path(id): Path<i32>,
|
||||||
|
State(data): State<Arc<AppState>>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
let query = sqlx::query_as(r#"SELECT * FROM tickets WHERE id = $1"#)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(&data.db)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match query {
|
||||||
|
Ok(ticket) => {
|
||||||
|
let ticket_response = serde_json::json!(filter_record(&ticket));
|
||||||
|
return Ok(Json(ticket_response));
|
||||||
|
}
|
||||||
|
Err(sqlx::Error::RowNotFound) => {
|
||||||
|
let error_response = serde_json::json!({
|
||||||
|
"status": "fail",
|
||||||
|
"message": format!("Ticket with ID {} not found", id)
|
||||||
|
});
|
||||||
|
return Err((StatusCode::NOT_FOUND, Json(error_response)));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({"status": "error", "message": format!("{:?}", e)})),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-6
@@ -1,10 +1,20 @@
|
|||||||
|
#![allow(unused_imports)]
|
||||||
|
mod handlers;
|
||||||
mod models;
|
mod models;
|
||||||
|
mod router;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{Router, routing};
|
use axum::{Router, routing};
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use models::*;
|
use models::*;
|
||||||
|
use router::create_router;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{PgPool, postgres::PgPoolOptions};
|
use sqlx::{PgPool, postgres::PgPoolOptions};
|
||||||
|
|
||||||
|
pub struct AppState {
|
||||||
|
db: PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
@@ -19,12 +29,7 @@ async fn main() {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let app = Router::new().route("/", routing::get(root_handler));
|
let app = create_router(Arc::new(AppState { db: pool.clone() }));
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8001").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:8001").await.unwrap();
|
||||||
axum::serve(listener, app).await;
|
axum::serve(listener, app).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn root_handler() -> &'static str {
|
|
||||||
"Hello, World"
|
|
||||||
}
|
|
||||||
|
|||||||
+63
-7
@@ -1,7 +1,10 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
use serde::{Deserialize, Serialize};
|
||||||
pub enum category {
|
use sqlx::{Decode, prelude::Type};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Type)]
|
||||||
|
pub enum Category {
|
||||||
WhiteboardBeamer,
|
WhiteboardBeamer,
|
||||||
Internet,
|
Internet,
|
||||||
IPadKoffer,
|
IPadKoffer,
|
||||||
@@ -10,21 +13,74 @@ pub enum category {
|
|||||||
Sonstiges,
|
Sonstiges,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
impl Display for Category {
|
||||||
pub struct ticket {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::WhiteboardBeamer => write!(f, "Whiteboard Beamer"),
|
||||||
|
Self::Internet => write!(f, "Internet"),
|
||||||
|
Self::IPadKoffer => write!(f, "IPad Koffer"),
|
||||||
|
Self::AppleTV => write!(f, "Apple TV"),
|
||||||
|
Self::DocuCam => write!(f, "Docu Cam"),
|
||||||
|
Self::Sonstiges => write!(f, "Sonstiges"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Type)]
|
||||||
|
pub enum Status {
|
||||||
|
ToDo,
|
||||||
|
InProgress,
|
||||||
|
Done,
|
||||||
|
Archived,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Status {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::ToDo => write!(f, "ToDo"),
|
||||||
|
Self::InProgress => write!(f, "InProgress"),
|
||||||
|
Self::Done => write!(f, "Done"),
|
||||||
|
Self::Archived => write!(f, "Archived"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, PartialEq, Debug, sqlx::FromRow)]
|
||||||
|
pub struct Ticket {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub category: category,
|
pub category: Category,
|
||||||
pub betreff: String,
|
pub betreff: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub room: i16,
|
pub room: i16,
|
||||||
|
pub status: Status,
|
||||||
|
pub date: chrono::NaiveDateTime,
|
||||||
|
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 date: chrono::NaiveDateTime,
|
||||||
pub user_id: i16,
|
pub user_id: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
||||||
pub struct user {
|
pub struct User {
|
||||||
pub id: i16,
|
pub id: i16,
|
||||||
pub first_name: String,
|
pub first_name: String,
|
||||||
pub last_name: String,
|
pub last_name: String,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct TicketCreateScheme {
|
||||||
|
pub category: Category,
|
||||||
|
pub betreff: String,
|
||||||
|
pub description: String,
|
||||||
|
pub room: i16,
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
Router,
|
||||||
|
routing::{get, post},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
handlers::ticket::{create_ticket, delete_ticket, get_ticket_by_id, get_tickets},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create_router(state: Arc<AppState>) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/api/tickets", get(get_tickets))
|
||||||
|
.route("/api/tickets/create", post(create_ticket))
|
||||||
|
.route(
|
||||||
|
"/api/tickets/{id}",
|
||||||
|
get(get_ticket_by_id).delete(delete_ticket),
|
||||||
|
)
|
||||||
|
.with_state(state)
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
DROP TYPE category;
|
||||||
|
DROP TYPE status;
|
||||||
@@ -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');
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
DROP TABLE tickets;
|
||||||
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
CREATE TYPE category AS ENUM('Whiteboard Beamer', 'Internet', 'iPad Koffer', 'Apple TV', 'Docu Cam', 'Sonstiges')
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tickets (
|
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',
|
category category NOT NULL DEFAULT 'Sonstiges',
|
||||||
betreff VARCHAR(100),
|
betreff VARCHAR(100),
|
||||||
description VARCHAR,
|
description TEXT,
|
||||||
room SMALLINT,
|
room SMALLINT,
|
||||||
date TIMESTAMP,
|
status status NOT NULL DEFAULT 'ToDo',
|
||||||
|
date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
user_id SMALLINT
|
user_id SMALLINT
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id SMALLINT PRIMARY KEY AUTOINCREMENT,
|
id SMALLINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
name VARCHAR(30),
|
name VARCHAR(30),
|
||||||
firstname VARCHAR(30),
|
firstname VARCHAR(30),
|
||||||
is_admin BOOLEAN NOT NULL DEFAULT false
|
is_admin BOOLEAN NOT NULL DEFAULT false
|
||||||
|
|||||||
Reference in New Issue
Block a user