🐈‍⬛🐈🐈🐈🐈🐈‍⬛

This commit is contained in:
Кобелев Андрей Андреевич 2022-10-13 10:00:04 +03:00
commit f312b82671
9 changed files with 2826 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
cats.db
.DS_Store

2512
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

18
Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "loyalty_rust"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sqlx = { version = "0.6", features = [ "runtime-async-std-native-tls" ] }
actix-web = "4"
config = "0.13.1"
serde = "1.0.8"
deadpool-postgres = { version = "0.10.2", features = ["serde"] }
tokio-postgres = "0.7.6"
rusqlite = { version = "0.28.0", features = ["bundled"] }
actix-files = "0.6"
env_logger = "0.9.0"
log = "0.4"

12
settings.yml Normal file
View File

@ -0,0 +1,12 @@
debug: false
host: 127.0.0.1
port: 8080
postgres:
db: pay
host: ekb-pay01
user: postgres
password: hdgKXR4BEFCb27vb
port: "5432"
schema: loyalty
prefix: ""

21
src/cfg/mod.rs Normal file
View File

@ -0,0 +1,21 @@
#[derive(Debug, Default, serde::Deserialize, PartialEq, Eq)]
pub struct AppConfig {
pub debug: bool,
pub host: String,
pub port: u16,
pub postgres: PostgresSettings,
}
#[derive(Debug, Default, serde::Deserialize, PartialEq, Eq)]
pub struct PostgresSettings {
db: String,
host: String,
user: String,
password: String,
port: String,
schema: String,
prefix: String,
}

133
src/loyalty/db/mod.rs Normal file
View File

@ -0,0 +1,133 @@
use rusqlite::{Connection, Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize)]
pub struct Cat {
id: i32,
name: String,
color: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Color {
id: i32,
name: String,
}
pub type CatsResult = Result<Vec<Cat>, Error>;
pub type ColorsResult = Result<Vec<Color>, Error>;
pub type CatResult = Result<Cat, Error>;
pub type ColorResult = Result<Color, Error>;
// Сервис базы данных
pub struct DbService {
db: rusqlite::Connection,
}
impl DbService {
// Создание сервиса
pub fn new() -> DbService {
let conn = Connection::open("cats.db");
let db_con = match conn {
Ok(conn) => conn,
Err(error) => panic!("Problem opening the db: {:?}", error),
};
db_con
.execute(
"create table if not exists cat_colors (
id integer primary key,
name text not null unique
)",
[],
)
.ok();
db_con
.execute(
"create table if not exists cats (
id integer primary key,
name text not null,
color_id integer not null references cat_colors(id)
)",
[],
)
.ok();
let mut cat_colors = HashMap::new();
cat_colors.insert(String::from("Blue"), vec!["Tigger", "Sammy"]);
cat_colors.insert(String::from("Black"), vec!["Oreo", "Biscuit"]);
for (color, catnames) in &cat_colors {
db_con
.execute(
"INSERT INTO cat_colors (name) values (?1)",
&[&color.to_string()],
)
.ok();
let last_id: String = db_con.last_insert_rowid().to_string();
for cat in catnames {
db_con
.execute(
"INSERT INTO cats (name, color_id) values (?1, ?2)",
&[&cat.to_string(), &last_id],
)
.ok();
}
}
return DbService { db: db_con };
}
// Получение кошек
pub fn get_cats(&self) -> CatsResult {
let mut stmt = self.db.prepare(
"SELECT c.id, c.name, cc.name from cats c INNER JOIN cat_colors cc ON cc.id = c.color_id;",
)?;
return stmt
.query_map([], |row| {
Ok(Cat {
id: row.get(0)?,
name: row.get(1)?,
color: row.get(2)?,
})
})
.and_then(Iterator::collect);
}
// Получение цветов
pub fn get_colors(&self) -> ColorsResult {
let mut stmt = self.db.prepare("SELECT id, name from cat_colors")?;
return stmt
.query_map([], |row| {
Ok(Color {
id: row.get(0)?,
name: row.get(1)?,
})
})
.and_then(Iterator::collect);
}
// Добавление кошки
pub fn add_cat(&self, cat_name: String, cat_color: String) -> CatResult {
return self.db.query_row("INSERT INTO cats (name, color_id) SELECT ?1, id FROM cat_colors cc WHERE name = ?2 RETURNING id, name, ?2",
[cat_name, cat_color],
|row| {
Ok(Cat {
id: row.get(0)?,
name: row.get(1)?,
color: row.get(2)?,
})
},
);
}
// Добавление цвета
pub fn add_color(&self, color_name: String) -> ColorResult {
return self.db.query_row(
"INSERT INTO cat_colors (name) VALUES (?1) RETURNING id, name",
[color_name],
|row| {
Ok(Color {
id: row.get(0)?,
name: row.get(1)?,
})
},
);
}
}

74
src/loyalty/mod.rs Normal file
View File

@ -0,0 +1,74 @@
pub mod db;
use actix_web::{get,post, web, HttpResponse, Responder, Result};
use serde::{Deserialize, Serialize};
pub struct LoyaltyService {
pub db: db::DbService,
}
#[derive(Serialize)]
struct ArrayResponse<T> {
result: Vec<T>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AddColorRequest {
pub name: String,
}
#[get("/cats")]
pub async fn get_cats(ctx: web::Data<LoyaltyService>) -> Result<impl Responder> {
let cats = ctx.db.get_cats();
let res = match cats {
Ok(v) => ArrayResponse{ result: v },
Err(_err) => ArrayResponse { result: vec![] },
};
return Ok(HttpResponse::Ok().json(res));
}
#[get("/colors")]
pub async fn get_colors(ctx: web::Data<LoyaltyService>) -> Result<impl Responder> {
let colors = ctx.db.get_colors();
let res = match colors {
Ok(v) => ArrayResponse { result: v },
Err(_err) => ArrayResponse { result: vec![] },
};
return Ok(HttpResponse::Ok().json(res));
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AddCatRequest {
pub name: String,
pub color: String,
}
#[post("/add/cat")]
pub async fn add_cat(
ctx: web::Data<LoyaltyService>,
cat: web::Json<AddCatRequest>,
) -> Result<impl Responder> {
let _cat = cat.into_inner();
let cat = ctx.db.add_cat(_cat.name, _cat.color);
let res = match cat {
Ok(v) => ArrayResponse { result: vec![v] },
Err(_err) => panic!("{:?}", _err),
};
return Ok(HttpResponse::Ok().json(res));
}
#[post("/add/color")]
pub async fn add_color(
ctx: web::Data<LoyaltyService>,
cat: web::Json<AddColorRequest>,
) -> Result<impl Responder> {
let _color = cat.into_inner();
let cat = ctx.db.add_color(_color.name);
let res = match cat {
Ok(v) => ArrayResponse { result: vec![v] },
Err(_err) => panic!("{:?}", _err),
};
return Ok(HttpResponse::Ok().json(res));
}

38
src/main.rs Normal file
View File

@ -0,0 +1,38 @@
use config::Config;
mod cfg;
mod loyalty;
use actix_web::{middleware::Logger, web, App, HttpServer};
use actix_files::Files;
#[actix_web::main]
pub async fn main() -> std::io::Result<()> {
let settings = Config::builder()
.add_source(config::File::with_name("settings.yml"))
.build()
.unwrap();
let app: cfg::AppConfig = settings.try_deserialize().unwrap();
println!(
"Запуск приложения в режиме {} {}:{}",
app.debug, app.host, app.port
);
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
log::info!("starting HTTP server at http://{}:{}", app.host, app.port);
HttpServer::new(|| {
App::new()
.app_data(web::Data::new(loyalty::LoyaltyService {
db: loyalty::db::DbService::new(),
}))
.service(Files::new("/", "./static/").index_file("index.html"))
.service(loyalty::get_cats)
.service(loyalty::get_colors)
.service(loyalty::add_cat)
.service(loyalty::add_color)
})
.bind((app.host, app.port))?
.run()
.await
}

15
static/index.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1> Hello cats!</h1>
</body>
</html>