From 264a45acf938bf6be7199f0371b3e78f72fe1568 Mon Sep 17 00:00:00 2001 From: Albert Shefner Date: Thu, 13 Nov 2025 16:22:32 +0300 Subject: [PATCH] Rework some code issues --- ...cb339fb38ba3e6457edb2a1d81b5defd99087.json | 56 ++++++++++++++ ...45e709059cf3ba0aaa2a0e598900c49244100.json | 14 ++++ ...3110e1ef36b707d99e600fae87379a9076437.json | 19 +++++ ...fe3310ce2af8d7dc314750df297c2e4baa560.json | 20 +++++ compose.yaml | 2 +- migrations/20251113124629_initial.down.sql | 2 + ...bles.sql => 20251113124629_initial.up.sql} | 4 +- queries/delete.sql | 4 + queries/get_all.sql | 10 +++ queries/insert.sql | 9 +++ queries/update.sql | 11 +++ src/database.rs | 25 +++--- src/main.rs | 10 +-- src/{router/game.rs => models.rs} | 12 +-- src/router.rs | 76 ++++++++++++++++--- src/router/funcs.rs | 53 ------------- 16 files changed, 239 insertions(+), 88 deletions(-) create mode 100644 .sqlx/query-2cdb2f4bbef873c327c73df8b01cb339fb38ba3e6457edb2a1d81b5defd99087.json create mode 100644 .sqlx/query-598954fb5ae205a2b8b06c30c5545e709059cf3ba0aaa2a0e598900c49244100.json create mode 100644 .sqlx/query-656f1dd85ab29925cc54579ee643110e1ef36b707d99e600fae87379a9076437.json create mode 100644 .sqlx/query-830a0cee1b7bdca78a623d93450fe3310ce2af8d7dc314750df297c2e4baa560.json create mode 100644 migrations/20251113124629_initial.down.sql rename migrations/{create_tables.sql => 20251113124629_initial.up.sql} (80%) create mode 100644 queries/delete.sql create mode 100644 queries/get_all.sql create mode 100644 queries/insert.sql create mode 100644 queries/update.sql rename src/{router/game.rs => models.rs} (79%) delete mode 100644 src/router/funcs.rs diff --git a/.sqlx/query-2cdb2f4bbef873c327c73df8b01cb339fb38ba3e6457edb2a1d81b5defd99087.json b/.sqlx/query-2cdb2f4bbef873c327c73df8b01cb339fb38ba3e6457edb2a1d81b5defd99087.json new file mode 100644 index 0000000..606af19 --- /dev/null +++ b/.sqlx/query-2cdb2f4bbef873c327c73df8b01cb339fb38ba3e6457edb2a1d81b5defd99087.json @@ -0,0 +1,56 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n id,\n name,\n publishing_house,\n developer,\n description,\n multiplayer,\n is_free_to_play\nFROM\n games\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "publishing_house", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "developer", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "multiplayer", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "is_free_to_play", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "2cdb2f4bbef873c327c73df8b01cb339fb38ba3e6457edb2a1d81b5defd99087" +} diff --git a/.sqlx/query-598954fb5ae205a2b8b06c30c5545e709059cf3ba0aaa2a0e598900c49244100.json b/.sqlx/query-598954fb5ae205a2b8b06c30c5545e709059cf3ba0aaa2a0e598900c49244100.json new file mode 100644 index 0000000..c4e4f0d --- /dev/null +++ b/.sqlx/query-598954fb5ae205a2b8b06c30c5545e709059cf3ba0aaa2a0e598900c49244100.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM\n games\nWHERE\n id = $1\n", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [] + }, + "hash": "598954fb5ae205a2b8b06c30c5545e709059cf3ba0aaa2a0e598900c49244100" +} diff --git a/.sqlx/query-656f1dd85ab29925cc54579ee643110e1ef36b707d99e600fae87379a9076437.json b/.sqlx/query-656f1dd85ab29925cc54579ee643110e1ef36b707d99e600fae87379a9076437.json new file mode 100644 index 0000000..044a380 --- /dev/null +++ b/.sqlx/query-656f1dd85ab29925cc54579ee643110e1ef36b707d99e600fae87379a9076437.json @@ -0,0 +1,19 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO games(\n name,\n publishing_house,\n developer,\n description,\n multiplayer,\n is_free_to_play\n)\nVALUES ($1, $2, $3, $4, $5, $6)\n", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text", + "Bool", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "656f1dd85ab29925cc54579ee643110e1ef36b707d99e600fae87379a9076437" +} diff --git a/.sqlx/query-830a0cee1b7bdca78a623d93450fe3310ce2af8d7dc314750df297c2e4baa560.json b/.sqlx/query-830a0cee1b7bdca78a623d93450fe3310ce2af8d7dc314750df297c2e4baa560.json new file mode 100644 index 0000000..4d417b4 --- /dev/null +++ b/.sqlx/query-830a0cee1b7bdca78a623d93450fe3310ce2af8d7dc314750df297c2e4baa560.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE\n games\nSET\n name = $1,\n publishing_house = $2,\n developer = $3,\n description = $4,\n multiplayer = $5,\n is_free_to_play = $6\nWHERE\n id = $7\n", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text", + "Bool", + "Bool", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "830a0cee1b7bdca78a623d93450fe3310ce2af8d7dc314750df297c2e4baa560" +} diff --git a/compose.yaml b/compose.yaml index 52922f9..cf1d860 100644 --- a/compose.yaml +++ b/compose.yaml @@ -6,4 +6,4 @@ services: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} ports: - - ${POSTGRES_PORT}:${POSTGRES_PORT} + - ${POSTGRES_PORT}:5432 diff --git a/migrations/20251113124629_initial.down.sql b/migrations/20251113124629_initial.down.sql new file mode 100644 index 0000000..b713899 --- /dev/null +++ b/migrations/20251113124629_initial.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +DROP TABLE games; diff --git a/migrations/create_tables.sql b/migrations/20251113124629_initial.up.sql similarity index 80% rename from migrations/create_tables.sql rename to migrations/20251113124629_initial.up.sql index 030340d..0b298a7 100644 --- a/migrations/create_tables.sql +++ b/migrations/20251113124629_initial.up.sql @@ -1,4 +1,6 @@ -CREATE TABLE IF NOT EXISTS games( +-- Add up migration script here + +CREATE TABLE games( id SERIAL PRIMARY KEY, name TEXT NOT NULL, publishing_house TEXT NOT NULL, diff --git a/queries/delete.sql b/queries/delete.sql new file mode 100644 index 0000000..5939c02 --- /dev/null +++ b/queries/delete.sql @@ -0,0 +1,4 @@ +DELETE FROM + games +WHERE + id = $1 diff --git a/queries/get_all.sql b/queries/get_all.sql new file mode 100644 index 0000000..8e06862 --- /dev/null +++ b/queries/get_all.sql @@ -0,0 +1,10 @@ +SELECT + id, + name, + publishing_house, + developer, + description, + multiplayer, + is_free_to_play +FROM + games diff --git a/queries/insert.sql b/queries/insert.sql new file mode 100644 index 0000000..0997b48 --- /dev/null +++ b/queries/insert.sql @@ -0,0 +1,9 @@ +INSERT INTO games( + name, + publishing_house, + developer, + description, + multiplayer, + is_free_to_play +) +VALUES ($1, $2, $3, $4, $5, $6) diff --git a/queries/update.sql b/queries/update.sql new file mode 100644 index 0000000..bb6e5f4 --- /dev/null +++ b/queries/update.sql @@ -0,0 +1,11 @@ +UPDATE + games +SET + name = $1, + publishing_house = $2, + developer = $3, + description = $4, + multiplayer = $5, + is_free_to_play = $6 +WHERE + id = $7 diff --git a/src/database.rs b/src/database.rs index 0861c5f..214a520 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,7 +1,14 @@ -use sqlx::PgPool; -use std::{env, fs}; +use std::env; -pub fn database_url() -> String { +use sqlx::PgPool; + +pub async fn create_pool() -> PgPool { + PgPool::connect(&database_url()) + .await + .expect("Could not connect to database") +} + +fn database_url() -> String { let _ = dotenvy::dotenv(); let database_url = format!( "postgres://{}:{}@{}:{}/{}", @@ -11,19 +18,11 @@ pub fn database_url() -> String { env::var("POSTGRES_PORT").unwrap(), env::var("POSTGRES_DB").unwrap(), ); + if database_url != env::var("DATABASE_URL").unwrap() { panic!("DATABASE_URL is not set correctly"); } + println!("DATABASE_URL={database_url}"); database_url } - -pub async fn create_table(pool: &PgPool) { - let sql_up = - fs::read_to_string("migrations/create_tables.sql").expect("Failed to read SQL file"); - - sqlx::query(&sql_up) - .execute(pool) - .await - .expect("Could not create database tables"); -} diff --git a/src/main.rs b/src/main.rs index af2a4e3..cc659dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ -use sqlx::PgPool; use tokio::net::TcpListener; mod app_state; mod database; +mod models; mod router; use app_state::AppState; @@ -10,15 +10,15 @@ use app_state::AppState; #[tokio::main] async fn main() { let state = AppState { - pool: PgPool::connect(&database::database_url()) - .await - .expect("Could not connect to database"), + pool: database::create_pool().await, }; - database::create_table(&state.pool); + sqlx::migrate!().run(&state.pool).await.unwrap(); let addr = "127.0.0.1:8000"; let listener = TcpListener::bind(addr).await.unwrap(); + println!("Listening at {addr}"); + axum::serve(listener, router::router(state)).await.unwrap(); } diff --git a/src/router/game.rs b/src/models.rs similarity index 79% rename from src/router/game.rs rename to src/models.rs index be80ddd..7933c5b 100644 --- a/src/router/game.rs +++ b/src/models.rs @@ -1,4 +1,6 @@ -#[derive(Clone, serde::Serialize, serde::Deserialize)] +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize)] pub struct Game { pub id: i32, pub name: String, @@ -19,7 +21,7 @@ pub struct Game { // tags: Option>, } -#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Serialize, Deserialize)] pub struct CreateGame { pub name: String, pub publishing_house: String, @@ -29,7 +31,7 @@ pub struct CreateGame { pub is_free_to_play: bool, } -#[derive(serde::Deserialize)] -pub struct GetGameQuery { - pub id: Option, +#[derive(Deserialize)] +pub struct GamePathParams { + pub id: i32, } diff --git a/src/router.rs b/src/router.rs index f753f25..f782968 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,19 +1,75 @@ use axum::routing::{get, post}; -pub mod funcs; -pub mod game; +use axum::{ + Json, + extract::{Path, State}, + response::IntoResponse, +}; use crate::app_state::AppState; +use crate::models::{CreateGame, Game, GamePathParams}; pub fn router(state: AppState) -> axum::Router { axum::Router::new() - .route( - "/api/games", - get(funcs::games_list).post(funcs::insert_game), - ) - .route( - "/api/games/{id}", - post(funcs::insert_game).delete(funcs::delete_game), - ) + .route("/api/games", get(games_list).post(insert_game)) + .route("/api/games/{id}", post(update_game).delete(delete_game)) .with_state(state) } + +pub async fn games_list(State(state): State) -> impl IntoResponse { + let games: Vec = sqlx::query_file_as!(Game, "queries/get_all.sql") + .fetch_all(&state.pool) + .await + .unwrap(); + + Json(games) +} + +pub async fn insert_game( + State(state): State, + Json(game): Json, +) -> impl IntoResponse { + sqlx::query_file!( + "queries/insert.sql", + game.name, + game.publishing_house, + game.developer, + game.description, + game.multiplayer, + game.is_free_to_play, + ) + .execute(&state.pool) + .await + .unwrap(); + + axum::http::StatusCode::CREATED +} + +pub async fn update_game( + State(state): State, + Path(params): Path, + Json(game): Json, +) -> impl IntoResponse { + sqlx::query_file!( + "queries/update.sql", + game.name, + game.publishing_house, + game.developer, + game.description, + game.multiplayer, + game.is_free_to_play, + params.id, + ) + .execute(&state.pool) + .await + .unwrap(); +} + +pub async fn delete_game(State(state): State, Path(id): Path) -> impl IntoResponse { + sqlx::query_file!("queries/delete.sql", id) + .execute(&state.pool) + .await + .unwrap(); + + axum::http::StatusCode::OK +} diff --git a/src/router/funcs.rs b/src/router/funcs.rs deleted file mode 100644 index 3b96c06..0000000 --- a/src/router/funcs.rs +++ /dev/null @@ -1,53 +0,0 @@ -use axum::{ - Json, - extract::{Path, Query, State}, - response::IntoResponse, -}; - -use super::game::{CreateGame, Game, GetGameQuery}; -use crate::app_state::AppState; - -pub async fn games_list(State(state): State) -> impl IntoResponse { - let games: Vec = sqlx::query_as!(Game, "SELECT * FROM games") - .fetch_all(&state.pool) - .await - .unwrap(); - - Json(games) -} - -pub async fn insert_game( - State(state): State, - Query(query): Query, - Json(game): Json, -) -> impl IntoResponse { - let sql_up = if query.id.is_some() { - "UPDATE games SET name=$1, publishing_house=$2, developer=$3, description=$4, multiplayer=$5, is_free_to_play=$6 WHERE id=$7" - } else { - "INSERT INTO games(name, publishing_house, developer, description, multiplayer, is_free_to_play) VALUES ($1, $2, $3, $4, $5, $6)" - }; - - let mut sql_query = sqlx::query(sql_up) - .bind(game.name) - .bind(game.publishing_house) - .bind(game.developer) - .bind(game.description) - .bind(game.multiplayer) - .bind(game.is_free_to_play); - - if let Some(id) = query.id { - sql_query = sql_query.bind(id); - } - sql_query.execute(&state.pool).await.unwrap(); - - axum::http::StatusCode::CREATED -} - -pub async fn delete_game(State(state): State, Path(id): Path) -> impl IntoResponse { - sqlx::query!("DELETE FROM games WHERE id = $1", id) - .execute(&state.pool) - .await - .unwrap(); - - axum::http::StatusCode::OK -}