/films implementation

This commit is contained in:
2025-11-12 13:57:40 +03:00
parent 5d7b1da363
commit d2d8799dd3
5 changed files with 620 additions and 55 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target
.env

424
Cargo.lock generated
View File

@ -273,6 +273,58 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b"
dependencies = [
"lazy_static",
"libm",
"num-integer",
"num-iter",
"num-traits",
"rand",
"smallvec",
"zeroize",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
name = "once_cell"
version = "1.21.3"
@ -320,6 +372,57 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs1"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
"der",
"pkcs8",
"spki",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
@ -449,6 +552,240 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "sqlx"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
dependencies = [
"sqlx-core",
"sqlx-macros",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
]
[[package]]
name = "sqlx-core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
dependencies = [
"base64",
"bytes",
"crc",
"crossbeam-queue",
"either",
"event-listener",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.15.5",
"hashlink",
"indexmap",
"log",
"memchr",
"once_cell",
"percent-encoding",
"serde",
"serde_json",
"sha2",
"smallvec",
"thiserror",
"time",
"tokio",
"tokio-stream",
"tracing",
"url",
]
[[package]]
name = "sqlx-macros"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
dependencies = [
"proc-macro2",
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn",
]
[[package]]
name = "sqlx-macros-core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
dependencies = [
"dotenvy",
"either",
"heck",
"hex",
"once_cell",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2",
"sqlx-core",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn",
"tokio",
"url",
]
[[package]]
name = "sqlx-mysql"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
"base64",
"bitflags",
"byteorder",
"bytes",
"crc",
"digest",
"dotenvy",
"either",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"generic-array",
"hex",
"hkdf",
"hmac",
"itoa",
"log",
"md-5",
"memchr",
"once_cell",
"percent-encoding",
"rand",
"rsa",
"serde",
"sha1",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"time",
"tracing",
"whoami",
]
[[package]]
name = "sqlx-postgres"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
"base64",
"bitflags",
"byteorder",
"crc",
"dotenvy",
"etcetera",
"futures-channel",
"futures-core",
"futures-util",
"hex",
"hkdf",
"hmac",
"home",
"itoa",
"log",
"md-5",
"memchr",
"once_cell",
"rand",
"serde",
"serde_json",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"time",
"tracing",
"whoami",
]
[[package]]
name = "sqlx-sqlite"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
dependencies = [
"atoi",
"flume",
"futures-channel",
"futures-core",
"futures-executor",
"futures-intrusive",
"futures-util",
"libsqlite3-sys",
"log",
"percent-encoding",
"serde",
"serde_urlencoded",
"sqlx-core",
"thiserror",
"time",
"tracing",
"url",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "stringprep"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"unicode-properties",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.109"
@ -466,6 +803,93 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]]
name = "time-macros"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.48.0"

View File

@ -4,5 +4,9 @@ version = "0.1.0"
edition = "2024"
[dependencies]
axum = { "0.8.6", features = ["full"]}
axum = "0.8.6"
chrono = "0.4.42"
dotenvy = "0.15.7"
serde = "1.0.228"
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "time", "macros"] }
tokio = { version = "1.48.0", features = ["full"]}

9
compose.yaml Normal file
View File

@ -0,0 +1,9 @@
services:
database:
image: "docker.io/postgres:17"
environment:
POSTGRES_DB: "my-db"
POSTGRES_USER: "user"
POSTGRES_PASSWORD: "pass"
ports:
- 5463:5432

View File

@ -1,77 +1,204 @@
use axum::response::IntoResponse;
use axum::{Router, routing::get};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use axum::{
Json, Router,
extract::{Path, Query, State},
http::StatusCode,
response::IntoResponse,
routing::get,
};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use tokio::net::TcpListener;
mod router;
#[derive(Clone)]
struct AppState {
pool: PgPool,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
async fn main() {
let _ = dotenvy::dotenv();
let app = Router::new()
.route("/", get(root))
.route(
"/films",
get(get_film)
.post(post_film)
.put(put_film)
.delete(delete_all),
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL should be set");
let pool = PgPool::connect(&database_url)
.await
.expect("Could not connect to database");
sqlx::raw_sql(
r#"
CREATE TABLE IF NOT EXISTS movie(
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
year DATE
);
"#,
)
.route("/films/{id}", get(get_film_by_id).delete(delete_film_by_id));
.execute(&pool)
.await
.expect("Couldn't create table");
loop {
let (mut socket, _) = listener.accept().await?;
let state = AppState { pool: pool };
tokio::spawn(async move {
let mut buf = [0; 1024];
let router = Router::new()
.route("/", get(root))
.route("/films", get(get_film).post(post_film).delete(delete_all))
.route(
"/films/{id}",
get(get_film_by_id).delete(delete_film_by_id).put(put_film),
)
.with_state(state);
// In a loop, read data from the socket and write the data back.
loop {
let n = match socket.read(&mut buf).await {
// socket closed
Ok(0) => return,
Ok(n) => n,
let addr = "127.0.0.1:8000";
let listener = TcpListener::bind(addr).await.unwrap();
println!("Listening at {addr}");
axum::serve(listener, router).await.unwrap();
}
#[derive(Clone, Deserialize, Serialize)]
struct Movie {
id: i32,
name: String,
year: i32,
}
#[derive(Clone, Deserialize, Serialize)]
struct CreateMovie {
name: String,
year: i32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
struct GetMovieQuery {
name: Option<String>,
year: Option<i32>,
}
async fn root() {}
async fn get_film(
State(state): State<AppState>,
Query(query): Query<GetMovieQuery>,
) -> impl IntoResponse {
let movies = sqlx::query_as!(Movie, "SELECT id, name, year FROM movies")
.fetch_all(&state.pool)
.await;
let movie = match movies {
Ok(_) => Json(movies.unwrap()),
Err(e) => {
eprintln!("failed to read from socket; err = {:?}", e);
return;
eprintln!("error: {e:?}");
Json(vec![])
}
};
// Write the data back
if let Err(e) = socket.write_all(&buf[0..n]).await {
eprintln!("failed to write to socket; err = {:?}", e);
return;
let movie_sorted: Vec<Movie> = movie
.to_vec()
.into_iter()
.filter(|m| query.name.as_ref().map_or(true, |n| m.name.contains(n)))
.filter(|m| query.year.map_or(true, |y| m.year == y))
.collect();
Json(movie_sorted)
}
async fn get_film_by_id(State(state): State<AppState>, Path(id): Path<i32>) -> impl IntoResponse {
let result = sqlx::query_as!(
Movie,
"SELECT id, name, year FROM movies WHERE id = ($1)",
id
)
.fetch_optional(&state.pool)
.await;
let movie = match result {
Ok(_) => result.unwrap(),
Err(e) => {
eprintln!("error: {e:?}");
None
}
});
};
match movie {
Some(m) => Json(m).into_response(),
None => (StatusCode::NOT_FOUND, "Movie not found").into_response(),
}
}
async fn root() {
axum::http::StatusCode::NOT_IMPLEMENTED
async fn post_film(State(state): State<AppState>, Json(movie): Json<Movie>) -> impl IntoResponse {
let result = sqlx::query!(
"INSERT INTO movies(name, year) VALUES ($1, $2)",
movie.name,
movie.year
)
.execute(&state.pool)
.await;
match result {
Ok(_) => (StatusCode::CREATED, "Movie has been created").into_response(),
Err(e) => {
eprintln!("error: {e:?}");
(StatusCode::INTERNAL_SERVER_ERROR).into_response()
}
}
}
async fn get_film() -> impl IntoResponse {
axum::http::StatusCode::NOT_IMPLEMENTED
async fn put_film(
State(state): State<AppState>,
Path(id): Path<i32>,
Json(movie): Json<CreateMovie>,
) -> impl IntoResponse {
let result = sqlx::query!(
"UPDATE movies
SET name = $1, year = $2
WHERE id = $3",
movie.name,
movie.year,
id
)
.execute(&state.pool)
.await;
match result {
Ok(res) => match res.rows_affected() {
0 => (StatusCode::NOT_FOUND, "Movie not found").into_response(),
_ => (StatusCode::CREATED, "Movie has been updated").into_response(),
},
Err(e) => {
eprintln!("error: {e:?}");
(StatusCode::INTERNAL_SERVER_ERROR).into_response()
}
}
}
async fn post_film() -> impl IntoResponse {
axum::http::StatusCode::NOT_IMPLEMENTED
async fn delete_film_by_id(
State(state): State<AppState>,
Path(id): Path<i32>,
) -> impl IntoResponse {
let result = sqlx::query!("DELETE FROM movies WHERE id = $1", id)
.execute(&state.pool)
.await;
match result {
Ok(res) => match res.rows_affected() {
0 => (StatusCode::NOT_FOUND, "Movie not found").into_response(),
_ => (StatusCode::NO_CONTENT, "Movie has been deleted").into_response(),
},
Err(e) => {
eprintln!("error: {e:?}");
(StatusCode::INTERNAL_SERVER_ERROR).into_response()
}
}
}
async fn put_film() -> impl IntoResponse {
axum::http::StatusCode::NOT_IMPLEMENTED
}
async fn delete_all(State(state): State<AppState>) -> impl IntoResponse {
let result = sqlx::query!("DELETE FROM movies")
.execute(&state.pool)
.await;
async fn delete_all() -> impl IntoResponse {
axum::http::StatusCode::NOT_IMPLEMENTED
match result {
Ok(_) => StatusCode::NOT_FOUND,
Err(e) => {
eprintln!("error: {e:?}");
StatusCode::INTERNAL_SERVER_ERROR
}
async fn get_film_by_id() -> impl IntoResponse {
axum::http::StatusCode::NOT_IMPLEMENTED
}
async fn delete_film_by_id() -> impl IntoResponse {
axum::http::StatusCode::NOT_IMPLEMENTED
}