add endpoints for price history and nutrients
This commit is contained in:
parent
8e3dd731c5
commit
340258e461
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT date, price_students, price_employees, price_guests FROM meals WHERE canteen = $1 AND LOWER(\"name\") = $2 AND is_latest = TRUE",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "date",
|
||||
"type_info": "Date"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "price_students",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "price_employees",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "price_guests",
|
||||
"type_info": "Numeric"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "1477fded8be083c2d5e29a92f9e558c9855ad90020dc1199bce0d20cca02799d"
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT date, canteen, price_students, price_employees, price_guests FROM meals WHERE LOWER(\"name\") = $1 AND is_latest = TRUE",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "date",
|
||||
"type_info": "Date"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "canteen",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "price_students",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "price_employees",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "price_guests",
|
||||
"type_info": "Numeric"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "56b320a7188c5ee88c869e763f6d23ee37461894a3b20c850908923460c3d3a0"
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT kjoules, proteins, carbohydrates, fats FROM meals m WHERE is_latest = TRUE AND LOWER(\"name\") = $1 ORDER BY date DESC LIMIT 1;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "kjoules",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "proteins",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "carbohydrates",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "fats",
|
||||
"type_info": "Numeric"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "a08588e594190460891c0e545b9983594e837f8da93db0d7c03e8ef16b9d0e3b"
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT date, canteen, price_students, price_employees, price_guests FROM meals WHERE canteen = ANY($1) AND LOWER(\"name\") = $2 AND is_latest = TRUE",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "date",
|
||||
"type_info": "Date"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "canteen",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "price_students",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "price_employees",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "price_guests",
|
||||
"type_info": "Numeric"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "cdf76d5e7d5d61d3cc61411d526816996885ccbd07237847c57f62cc3b6357db"
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT kjoules, proteins, carbohydrates, fats FROM meals m WHERE is_latest = TRUE AND LOWER(\"name\") = $1 AND date = $2 LIMIT 1;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "kjoules",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "proteins",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "carbohydrates",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "fats",
|
||||
"type_info": "Numeric"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Date"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "d7d20b101fbed8dfe7ff33ac7a6a0e4cddfaa36050c5482818b6ee4783f8173d"
|
||||
}
|
||||
|
|
@ -1527,7 +1527,7 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
|||
|
||||
[[package]]
|
||||
name = "mensa-upb-api"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"actix-cors",
|
||||
"actix-governor",
|
||||
|
|
@ -1550,7 +1550,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mensa-upb-scraper"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
|
|
@ -1895,7 +1895,7 @@ dependencies = [
|
|||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.5.10",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
@ -1932,9 +1932,9 @@ dependencies = [
|
|||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ license.workspace = true
|
|||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
readme.workspace = true
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ pub struct Dish {
|
|||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct NutritionValues {
|
||||
pub kjoule: Option<i64>,
|
||||
pub kjoule: Option<i32>,
|
||||
pub protein: Option<BigDecimal>,
|
||||
pub carbs: Option<BigDecimal>,
|
||||
pub fat: Option<BigDecimal>,
|
||||
|
|
|
|||
|
|
@ -41,8 +41,6 @@ pub async fn scrape_menu(date: &NaiveDate, canteen: Canteen) -> Result<Vec<Dish>
|
|||
res.extend(side_dishes);
|
||||
res.extend(desserts);
|
||||
|
||||
dbg!(&res);
|
||||
|
||||
tracing::debug!("Finished scraping");
|
||||
|
||||
Ok(res)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use strum::EnumIter;
|
|||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumIter, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Canteen {
|
||||
Forum,
|
||||
Academica,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ license.workspace = true
|
|||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
readme.workspace = true
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use bigdecimal::BigDecimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared::Canteen;
|
||||
use sqlx::prelude::FromRow;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Dish {
|
||||
|
|
@ -19,6 +20,14 @@ pub struct DishPrices {
|
|||
pub guests: BigDecimal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, FromRow)]
|
||||
pub struct DishNutrients {
|
||||
pub kjoules: Option<i32>,
|
||||
pub carbohydrates: Option<BigDecimal>,
|
||||
pub proteins: Option<BigDecimal>,
|
||||
pub fats: Option<BigDecimal>,
|
||||
}
|
||||
|
||||
impl Dish {
|
||||
pub fn same_as(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
|
|
@ -39,3 +48,24 @@ impl PartialOrd for Dish {
|
|||
self.name.partial_cmp(&other.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl DishPrices {
|
||||
pub fn normalize(self) -> Self {
|
||||
Self {
|
||||
students: self.students.with_prec(5).with_scale(2),
|
||||
employees: self.employees.with_prec(5).with_scale(2),
|
||||
guests: self.guests.with_prec(5).with_scale(2),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DishNutrients {
|
||||
pub fn normalize(self) -> Self {
|
||||
Self {
|
||||
kjoules: self.kjoules,
|
||||
carbohydrates: self.carbohydrates.map(|v| v.with_prec(6).with_scale(2)),
|
||||
proteins: self.proteins.map(|v| v.with_prec(6).with_scale(2)),
|
||||
fats: self.fats.map(|v| v.with_prec(6).with_scale(2)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,35 @@
|
|||
use std::str::FromStr as _;
|
||||
|
||||
use actix_web::{get, web, HttpResponse, Responder};
|
||||
use actix_web::{
|
||||
get,
|
||||
web::{self, ServiceConfig},
|
||||
HttpResponse, Responder,
|
||||
};
|
||||
use chrono::NaiveDate;
|
||||
use itertools::Itertools as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use shared::Canteen;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::Menu;
|
||||
use crate::{util, Menu};
|
||||
|
||||
pub fn configure(cfg: &mut ServiceConfig) {
|
||||
cfg.service(menu);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct MenuQuery {
|
||||
date: Option<NaiveDate>,
|
||||
no_update: Option<bool>,
|
||||
#[serde(default)]
|
||||
no_update: bool,
|
||||
}
|
||||
|
||||
#[get("/menu/{canteen}")]
|
||||
async fn menu(
|
||||
path: web::Path<String>,
|
||||
query: web::Query<MenuQuery>,
|
||||
db: web::Data<PgPool>,
|
||||
) -> impl Responder {
|
||||
let canteens = path
|
||||
.into_inner()
|
||||
.split(',')
|
||||
.map(Canteen::from_str)
|
||||
.collect_vec();
|
||||
let canteens = util::parse_canteens_comma_separated(&path);
|
||||
if canteens.iter().all(Result::is_ok) {
|
||||
let canteens = canteens.into_iter().filter_map(Result::ok).collect_vec();
|
||||
|
||||
|
|
@ -34,15 +37,17 @@ async fn menu(
|
|||
.date
|
||||
.unwrap_or_else(|| chrono::Local::now().date_naive());
|
||||
|
||||
let menu = Menu::query(&db, date, &canteens, !query.no_update.unwrap_or_default()).await;
|
||||
let menu = Menu::query(&db, date, &canteens, !query.no_update).await;
|
||||
|
||||
if let Ok(menu) = menu {
|
||||
HttpResponse::Ok().json(menu)
|
||||
} else {
|
||||
match menu {
|
||||
Ok(menu) => HttpResponse::Ok().json(menu),
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to query database: {err:?}");
|
||||
HttpResponse::InternalServerError().json(json!({
|
||||
"error": "Failed to query database",
|
||||
}))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
HttpResponse::BadRequest().json(json!({
|
||||
"error": "Invalid canteen identifier",
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@ use shared::Canteen;
|
|||
use strum::IntoEnumIterator as _;
|
||||
|
||||
mod menu;
|
||||
mod nutrition;
|
||||
mod price_history;
|
||||
|
||||
pub fn configure(cfg: &mut ServiceConfig) {
|
||||
cfg.service(index);
|
||||
cfg.service(menu::menu);
|
||||
cfg.service(index)
|
||||
.configure(menu::configure)
|
||||
.configure(nutrition::configure)
|
||||
.configure(price_history::configure);
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
use actix_web::{
|
||||
get,
|
||||
web::{self, ServiceConfig},
|
||||
HttpResponse, Responder,
|
||||
};
|
||||
use chrono::NaiveDate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::dish::DishNutrients;
|
||||
|
||||
pub fn configure(cfg: &mut ServiceConfig) {
|
||||
cfg.service(nutrition);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct NutritionQuery {
|
||||
date: Option<NaiveDate>,
|
||||
}
|
||||
|
||||
#[get("/nutrition/{name}")]
|
||||
async fn nutrition(
|
||||
path: web::Path<String>,
|
||||
query: web::Query<NutritionQuery>,
|
||||
db: web::Data<PgPool>,
|
||||
) -> impl Responder {
|
||||
let db = db.as_ref();
|
||||
let dish_name = path.into_inner();
|
||||
|
||||
let res = if let Some(date) = query.date {
|
||||
sqlx::query_as!(
|
||||
DishNutrients,
|
||||
r#"SELECT kjoules, proteins, carbohydrates, fats FROM meals m WHERE is_latest = TRUE AND LOWER("name") = $1 AND date = $2 LIMIT 1;"#,
|
||||
dish_name.to_lowercase(),
|
||||
date,
|
||||
).fetch_optional(db).await
|
||||
} else {
|
||||
sqlx::query_as!(
|
||||
DishNutrients,
|
||||
r#"SELECT kjoules, proteins, carbohydrates, fats FROM meals m WHERE is_latest = TRUE AND LOWER("name") = $1 ORDER BY date DESC LIMIT 1;"#,
|
||||
dish_name.to_lowercase(),
|
||||
).fetch_optional(db).await
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(Some(nutrition)) => HttpResponse::Ok().json(nutrition.normalize()),
|
||||
Ok(None) => HttpResponse::NotFound().json(json!({
|
||||
"error": "Dish cannot be found",
|
||||
})),
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to query database: {err:?}");
|
||||
HttpResponse::InternalServerError().json(json!({
|
||||
"error": "Failed to query database",
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use actix_web::{
|
||||
get,
|
||||
web::{self, ServiceConfig},
|
||||
HttpResponse, Responder,
|
||||
};
|
||||
use bigdecimal::BigDecimal;
|
||||
use chrono::NaiveDate;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::{prelude::FromRow, PgPool};
|
||||
|
||||
use crate::{util, DishPrices};
|
||||
|
||||
pub fn configure(cfg: &mut ServiceConfig) {
|
||||
cfg.service(price_history);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PriceHistoryQuery {
|
||||
canteens: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, FromRow)]
|
||||
struct PriceHistoryRow {
|
||||
date: NaiveDate,
|
||||
canteen: String,
|
||||
price_students: BigDecimal,
|
||||
price_employees: BigDecimal,
|
||||
price_guests: BigDecimal,
|
||||
}
|
||||
|
||||
#[get("/price-history/{name}")]
|
||||
async fn price_history(
|
||||
path: web::Path<String>,
|
||||
query: web::Query<PriceHistoryQuery>,
|
||||
db: web::Data<PgPool>,
|
||||
) -> impl Responder {
|
||||
let db = db.as_ref();
|
||||
let canteens = query
|
||||
.canteens
|
||||
.as_deref()
|
||||
.map(util::parse_canteens_comma_separated);
|
||||
let dish_name = path.into_inner();
|
||||
|
||||
if let Some(canteens) = canteens {
|
||||
if canteens.iter().all(Result::is_ok) {
|
||||
let canteens = canteens.into_iter().filter_map(Result::ok).collect_vec();
|
||||
|
||||
if canteens.len() == 1 {
|
||||
let canteen = canteens.into_iter().next().expect("length is 1");
|
||||
|
||||
let res = sqlx::query!(
|
||||
r#"SELECT date, price_students, price_employees, price_guests FROM meals WHERE canteen = $1 AND LOWER("name") = $2 AND is_latest = TRUE"#,
|
||||
canteen.get_identifier(),
|
||||
dish_name.to_lowercase(),
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(recs) => {
|
||||
let structured = recs
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
(
|
||||
r.date,
|
||||
DishPrices {
|
||||
students: r.price_students,
|
||||
employees: r.price_employees,
|
||||
guests: r.price_guests,
|
||||
}
|
||||
.normalize(),
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
HttpResponse::Ok().json(structured)
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to query database: {err:?}");
|
||||
HttpResponse::InternalServerError().json(json!({
|
||||
"error": "Failed to query database",
|
||||
}))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let res = sqlx::query_as!(PriceHistoryRow,
|
||||
r#"SELECT date, canteen, price_students, price_employees, price_guests FROM meals WHERE canteen = ANY($1) AND LOWER("name") = $2 AND is_latest = TRUE"#,
|
||||
&canteens.iter().map(|c| c.get_identifier().to_string()).collect_vec(),
|
||||
dish_name.to_lowercase(),
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(recs) => {
|
||||
let structured = structure_multiple_canteens(recs);
|
||||
|
||||
HttpResponse::Ok().json(structured)
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to query database: {err:?}");
|
||||
HttpResponse::InternalServerError().json(json!({
|
||||
"error": "Failed to query database",
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
HttpResponse::BadRequest().json(json!({
|
||||
"error": "Invalid canteen identifier",
|
||||
"invalid": canteens.into_iter().filter_map(|c| c.err()).collect_vec()
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
let res = sqlx::query_as!(PriceHistoryRow,
|
||||
r#"SELECT date, canteen, price_students, price_employees, price_guests FROM meals WHERE LOWER("name") = $1 AND is_latest = TRUE"#,
|
||||
dish_name.to_lowercase(),
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(recs) => {
|
||||
let structured = structure_multiple_canteens(recs);
|
||||
|
||||
HttpResponse::Ok().json(structured)
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to query database: {err:?}");
|
||||
HttpResponse::InternalServerError().json(json!({
|
||||
"error": "Failed to query database",
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn structure_multiple_canteens(
|
||||
v: Vec<PriceHistoryRow>,
|
||||
) -> BTreeMap<String, BTreeMap<NaiveDate, DishPrices>> {
|
||||
v.into_iter()
|
||||
.chunk_by(|r| r.canteen.clone())
|
||||
.into_iter()
|
||||
.map(|(d, g)| {
|
||||
(
|
||||
d,
|
||||
g.map(|r| {
|
||||
(
|
||||
r.date,
|
||||
DishPrices {
|
||||
students: r.price_students,
|
||||
employees: r.price_employees,
|
||||
guests: r.price_guests,
|
||||
}
|
||||
.normalize(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ mod dish;
|
|||
pub mod endpoints;
|
||||
mod governor;
|
||||
mod menu;
|
||||
mod util;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ use std::env;
|
|||
|
||||
use actix_cors::Cors;
|
||||
use actix_governor::Governor;
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use actix_web::{
|
||||
middleware::{self, NormalizePath},
|
||||
web, App, HttpServer,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use mensa_upb_api::get_governor;
|
||||
|
|
@ -72,6 +75,7 @@ async fn main() -> Result<()> {
|
|||
.allow_any_header()
|
||||
.max_age(3600);
|
||||
App::new()
|
||||
.wrap(NormalizePath::new(middleware::TrailingSlash::Trim))
|
||||
.wrap(Governor::new(&governor_conf))
|
||||
.wrap(cors)
|
||||
.app_data(web::Data::new(db.clone()))
|
||||
|
|
|
|||
|
|
@ -59,10 +59,11 @@ impl Menu {
|
|||
vegan: row.vegan,
|
||||
vegetarian: row.vegetarian,
|
||||
price: DishPrices {
|
||||
students: row.price_students.with_prec(5).with_scale(2),
|
||||
employees: row.price_employees.with_prec(5).with_scale(2),
|
||||
guests: row.price_guests.with_prec(5).with_scale(2),
|
||||
},
|
||||
students: row.price_students,
|
||||
employees: row.price_employees,
|
||||
guests: row.price_guests,
|
||||
}
|
||||
.normalize(),
|
||||
};
|
||||
if row.dish_type == DishType::Main {
|
||||
main_dishes.push(dish);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
use std::str::FromStr as _;
|
||||
|
||||
use shared::Canteen;
|
||||
|
||||
pub fn parse_canteens_comma_separated(s: &str) -> Vec<Result<Canteen, String>> {
|
||||
s.split(',').map(Canteen::from_str).collect()
|
||||
}
|
||||
Loading…
Reference in New Issue