mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Separate the api datamodel into a separate crate
This commit is contained in:
parent
066fa8648d
commit
db63deb319
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -53,6 +53,15 @@ version = "1.0.62"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305"
|
checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "api"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"recipes",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "argon2"
|
name = "argon2"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -1262,6 +1271,7 @@ dependencies = [
|
|||||||
name = "kitchen"
|
name = "kitchen"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"api",
|
||||||
"argon2",
|
"argon2",
|
||||||
"async-session",
|
"async-session",
|
||||||
"async-std",
|
"async-std",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [ "recipes", "kitchen", "web" ]
|
members = [ "recipes", "kitchen", "web", "api" ]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# TODO(jwall): When the fix for RcSignal Binding is released we can drop this patch.
|
# TODO(jwall): When the fix for RcSignal Binding is released we can drop this patch.
|
||||||
|
18
api/Cargo.toml
Normal file
18
api/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "api"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = "1.0.144"
|
||||||
|
recipes = { path = "../recipes" }
|
||||||
|
|
||||||
|
[dependencies.axum]
|
||||||
|
version = "0.5.16"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
server = ["axum"]
|
||||||
|
browser = []
|
163
api/src/lib.rs
Normal file
163
api/src/lib.rs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use axum::{
|
||||||
|
self,
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response as AxumResponse},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use recipes::{IngredientKey, RecipeEntry};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum Response<T> {
|
||||||
|
Success(T),
|
||||||
|
Err { status: u16, message: String },
|
||||||
|
NotFound,
|
||||||
|
Unauthorized,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Response<T> {
|
||||||
|
pub fn error<S: Into<String>>(code: u16, msg: S) -> Self {
|
||||||
|
Self::Err {
|
||||||
|
status: code,
|
||||||
|
message: msg.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn success(payload: T) -> Self {
|
||||||
|
Self::Success(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl<T> IntoResponse for Response<T>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
fn into_response(self) -> AxumResponse {
|
||||||
|
match &self {
|
||||||
|
Self::Success(_) => (StatusCode::OK, axum::Json::from(self)).into_response(),
|
||||||
|
Self::Err { status, message: _ } => {
|
||||||
|
let code = match StatusCode::from_u16(*status) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
};
|
||||||
|
(code, axum::Json::from(self)).into_response()
|
||||||
|
}
|
||||||
|
Self::NotFound => (StatusCode::NOT_FOUND, axum::Json::from(self)).into_response(),
|
||||||
|
Self::Unauthorized => {
|
||||||
|
(StatusCode::UNAUTHORIZED, axum::Json::from(self)).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Result<T, String>> for Response<T> {
|
||||||
|
fn from(val: Result<T, String>) -> Self {
|
||||||
|
match val {
|
||||||
|
Ok(val) => Response::Success(val),
|
||||||
|
Err(e) => Response::error(500, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Result<Option<T>, String>> for Response<T>
|
||||||
|
where
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
fn from(val: Result<Option<T>, String>) -> Self {
|
||||||
|
match val {
|
||||||
|
Ok(Some(val)) => Response::Success(val),
|
||||||
|
Ok(None) => Response::Success(T::default()),
|
||||||
|
Err(e) => Response::error(500, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct UserData {
|
||||||
|
pub user_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AccountResponse = Response<UserData>;
|
||||||
|
|
||||||
|
impl From<UserData> for AccountResponse {
|
||||||
|
fn from(user_data: UserData) -> Self {
|
||||||
|
Response::Success(user_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type RecipeEntryResponse = Response<Vec<RecipeEntry>>;
|
||||||
|
|
||||||
|
impl From<Vec<RecipeEntry>> for RecipeEntryResponse {
|
||||||
|
fn from(entries: Vec<RecipeEntry>) -> Self {
|
||||||
|
Response::Success(entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PlanDataResponse = Response<Vec<(String, i32)>>;
|
||||||
|
|
||||||
|
impl From<Vec<(String, i32)>> for PlanDataResponse {
|
||||||
|
fn from(plan: Vec<(String, i32)>) -> Self {
|
||||||
|
Response::Success(plan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Option<Vec<(String, i32)>>> for PlanDataResponse {
|
||||||
|
fn from(plan: Option<Vec<(String, i32)>>) -> Self {
|
||||||
|
match plan {
|
||||||
|
Some(plan) => Response::Success(plan),
|
||||||
|
None => Response::Success(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct InventoryData {
|
||||||
|
pub filtered_ingredients: Vec<IngredientKey>,
|
||||||
|
pub modified_amts: Vec<(IngredientKey, String)>,
|
||||||
|
pub extra_items: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type InventoryResponse = Response<InventoryData>;
|
||||||
|
|
||||||
|
impl
|
||||||
|
From<(
|
||||||
|
Vec<IngredientKey>,
|
||||||
|
Vec<(IngredientKey, String)>,
|
||||||
|
Vec<(String, String)>,
|
||||||
|
)> for InventoryData
|
||||||
|
{
|
||||||
|
fn from(
|
||||||
|
(filtered_ingredients, modified_amts, extra_items): (
|
||||||
|
Vec<IngredientKey>,
|
||||||
|
Vec<(IngredientKey, String)>,
|
||||||
|
Vec<(String, String)>,
|
||||||
|
),
|
||||||
|
) -> Self {
|
||||||
|
InventoryData {
|
||||||
|
filtered_ingredients,
|
||||||
|
modified_amts,
|
||||||
|
extra_items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InventoryData> for InventoryResponse {
|
||||||
|
fn from(inventory_data: InventoryData) -> Self {
|
||||||
|
Response::Success(inventory_data)
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ edition = "2021"
|
|||||||
tracing = "0.1.35"
|
tracing = "0.1.35"
|
||||||
tracing-subscriber = "0.3.14"
|
tracing-subscriber = "0.3.14"
|
||||||
recipes = { path = "../recipes" }
|
recipes = { path = "../recipes" }
|
||||||
|
api = { path = "../api" }
|
||||||
csv = "1.1.1"
|
csv = "1.1.1"
|
||||||
rust-embed="6.4.0"
|
rust-embed="6.4.0"
|
||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use api;
|
||||||
use async_session::{Session, SessionStore};
|
use async_session::{Session, SessionStore};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::Extension,
|
extract::Extension,
|
||||||
@ -22,31 +23,15 @@ use axum::{
|
|||||||
use axum_auth::AuthBasic;
|
use axum_auth::AuthBasic;
|
||||||
use cookie::{Cookie, SameSite};
|
use cookie::{Cookie, SameSite};
|
||||||
use secrecy::Secret;
|
use secrecy::Secret;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tracing::{debug, error, info, instrument};
|
use tracing::{debug, error, info, instrument};
|
||||||
|
|
||||||
use super::storage::{self, AuthStore, UserCreds};
|
use super::storage::{self, AuthStore, UserCreds};
|
||||||
|
|
||||||
// FIXME(jwall): This needs to live in a client integration library.
|
impl From<UserCreds> for api::AccountResponse {
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub enum AccountResponse {
|
|
||||||
Success { user_id: String },
|
|
||||||
Err { message: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<UserCreds> for AccountResponse {
|
|
||||||
fn from(auth: UserCreds) -> Self {
|
fn from(auth: UserCreds) -> Self {
|
||||||
Self::Success {
|
Self::Success(api::UserData {
|
||||||
user_id: auth.user_id().to_owned(),
|
user_id: auth.user_id().to_owned(),
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for AccountResponse {
|
|
||||||
fn from(msg: &'a str) -> Self {
|
|
||||||
Self::Err {
|
|
||||||
message: msg.to_string(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +39,7 @@ impl<'a> From<&'a str> for AccountResponse {
|
|||||||
pub async fn handler(
|
pub async fn handler(
|
||||||
auth: AuthBasic,
|
auth: AuthBasic,
|
||||||
Extension(session_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(session_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
) -> (StatusCode, HeaderMap, axum::Json<AccountResponse>) {
|
) -> (StatusCode, HeaderMap, axum::Json<api::AccountResponse>) {
|
||||||
// NOTE(jwall): It is very important that you do **not** log the password
|
// NOTE(jwall): It is very important that you do **not** log the password
|
||||||
// here. We convert the AuthBasic into UserCreds immediately to help prevent
|
// here. We convert the AuthBasic into UserCreds immediately to help prevent
|
||||||
// that. Do not circumvent that protection.
|
// that. Do not circumvent that protection.
|
||||||
@ -67,7 +52,10 @@ pub async fn handler(
|
|||||||
let mut session = Session::new();
|
let mut session = Session::new();
|
||||||
if let Err(err) = session.insert("user_id", auth.user_id()) {
|
if let Err(err) = session.insert("user_id", auth.user_id()) {
|
||||||
error!(?err, "Unable to insert user id into session");
|
error!(?err, "Unable to insert user id into session");
|
||||||
let resp: AccountResponse = "Unable to insert user id into session".into();
|
let resp = api::AccountResponse::error(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
|
||||||
|
"Unable to insert user id into session",
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
headers,
|
headers,
|
||||||
@ -78,7 +66,10 @@ pub async fn handler(
|
|||||||
let cookie_value = match session_store.store_session(session).await {
|
let cookie_value = match session_store.store_session(session).await {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "Unable to store session in session store");
|
error!(?err, "Unable to store session in session store");
|
||||||
let resp: AccountResponse = "Unable to store session in session store".into();
|
let resp = api::AccountResponse::error(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
|
||||||
|
"Unable to store session in session store",
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
headers,
|
headers,
|
||||||
@ -87,7 +78,10 @@ pub async fn handler(
|
|||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
error!("Unable to create session cookie");
|
error!("Unable to create session cookie");
|
||||||
let resp: AccountResponse = "Unable to create session cookie".into();
|
let resp = api::AccountResponse::error(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
|
||||||
|
"Unable to create session cookie",
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
headers,
|
headers,
|
||||||
@ -105,7 +99,10 @@ pub async fn handler(
|
|||||||
let parsed_cookie = match cookie.to_string().parse() {
|
let parsed_cookie = match cookie.to_string().parse() {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "Unable to parse session cookie");
|
error!(?err, "Unable to parse session cookie");
|
||||||
let resp: AccountResponse = "Unable to parse session cookie".into();
|
let resp = api::AccountResponse::error(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
|
||||||
|
"Unable to parse session cookie",
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
headers,
|
headers,
|
||||||
@ -116,12 +113,15 @@ pub async fn handler(
|
|||||||
};
|
};
|
||||||
headers.insert(header::SET_COOKIE, parsed_cookie);
|
headers.insert(header::SET_COOKIE, parsed_cookie);
|
||||||
// Respond with 200 OK
|
// Respond with 200 OK
|
||||||
let resp: AccountResponse = auth.into();
|
let resp: api::AccountResponse = auth.into();
|
||||||
(StatusCode::OK, headers, axum::Json::from(resp))
|
(StatusCode::OK, headers, axum::Json::from(resp))
|
||||||
} else {
|
} else {
|
||||||
debug!("Invalid credentials");
|
debug!("Invalid credentials");
|
||||||
let headers = HeaderMap::new();
|
let headers = HeaderMap::new();
|
||||||
let resp: AccountResponse = "Invalid user id or password".into();
|
let resp = api::AccountResponse::error(
|
||||||
|
StatusCode::UNAUTHORIZED.as_u16(),
|
||||||
|
"Invalid user id or password",
|
||||||
|
);
|
||||||
(StatusCode::UNAUTHORIZED, headers, axum::Json::from(resp))
|
(StatusCode::UNAUTHORIZED, headers, axum::Json::from(resp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,13 +23,15 @@ use axum::{
|
|||||||
response::{IntoResponse, Redirect, Response},
|
response::{IntoResponse, Redirect, Response},
|
||||||
routing::{get, Router},
|
routing::{get, Router},
|
||||||
};
|
};
|
||||||
|
use chrono::NaiveDate;
|
||||||
use mime_guess;
|
use mime_guess;
|
||||||
use recipes::{IngredientKey, RecipeEntry};
|
use recipes::{IngredientKey, RecipeEntry};
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use tower::ServiceBuilder;
|
use tower::ServiceBuilder;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::{debug, error, info, instrument};
|
use tracing::{debug, info, instrument};
|
||||||
|
|
||||||
|
use api;
|
||||||
use storage::{APIStore, AuthStore};
|
use storage::{APIStore, AuthStore};
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
@ -83,7 +85,7 @@ async fn api_recipe_entry(
|
|||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
Path(recipe_id): Path<String>,
|
Path(recipe_id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> api::Response<Option<RecipeEntry>> {
|
||||||
use storage::{UserId, UserIdFromSession::*};
|
use storage::{UserId, UserIdFromSession::*};
|
||||||
let result = match session {
|
let result = match session {
|
||||||
NoUserId => store
|
NoUserId => store
|
||||||
@ -95,11 +97,7 @@ async fn api_recipe_entry(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Error: {:?}", e)),
|
.map_err(|e| format!("Error: {:?}", e)),
|
||||||
};
|
};
|
||||||
match result {
|
result.into()
|
||||||
Ok(Some(recipes)) => (StatusCode::OK, axum::Json::from(recipes)).into_response(),
|
|
||||||
Ok(None) => (StatusCode::NOT_FOUND, axum::Json::from("")).into_response(),
|
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, axum::Json::from(e)).into_response(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
@ -107,7 +105,7 @@ async fn api_recipes(
|
|||||||
Extension(store): Extension<Arc<storage::file_store::AsyncFileStore>>,
|
Extension(store): Extension<Arc<storage::file_store::AsyncFileStore>>,
|
||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
) -> impl IntoResponse {
|
) -> api::RecipeEntryResponse {
|
||||||
// Select recipes based on the user-id if it exists or serve the default if it does not.
|
// Select recipes based on the user-id if it exists or serve the default if it does not.
|
||||||
use storage::{UserId, UserIdFromSession::*};
|
use storage::{UserId, UserIdFromSession::*};
|
||||||
let result = match session {
|
let result = match session {
|
||||||
@ -120,11 +118,7 @@ async fn api_recipes(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Error: {:?}", e)),
|
.map_err(|e| format!("Error: {:?}", e)),
|
||||||
};
|
};
|
||||||
match result {
|
result.into()
|
||||||
Ok(Some(recipes)) => Ok(axum::Json::from(recipes)),
|
|
||||||
Ok(None) => Ok(axum::Json::from(Vec::<RecipeEntry>::new())),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
@ -132,7 +126,7 @@ async fn api_categories(
|
|||||||
Extension(store): Extension<Arc<storage::file_store::AsyncFileStore>>,
|
Extension(store): Extension<Arc<storage::file_store::AsyncFileStore>>,
|
||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
) -> impl IntoResponse {
|
) -> api::Response<String> {
|
||||||
// Select Categories based on the user-id if it exists or serve the default if it does not.
|
// Select Categories based on the user-id if it exists or serve the default if it does not.
|
||||||
use storage::{UserId, UserIdFromSession::*};
|
use storage::{UserId, UserIdFromSession::*};
|
||||||
let categories_result = match session {
|
let categories_result = match session {
|
||||||
@ -145,33 +139,28 @@ async fn api_categories(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Error: {:?}", e)),
|
.map_err(|e| format!("Error: {:?}", e)),
|
||||||
};
|
};
|
||||||
let result: Result<axum::Json<String>, String> = match categories_result {
|
categories_result.into()
|
||||||
Ok(Some(categories)) => Ok(axum::Json::from(categories)),
|
|
||||||
Ok(None) => Ok(axum::Json::from(String::new())),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
};
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn api_save_categories(
|
async fn api_save_categories(
|
||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
Json(categories): Json<String>,
|
Json(categories): Json<String>,
|
||||||
) -> impl IntoResponse {
|
) -> api::Response<String> {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
if let Err(e) = app_store
|
if let Err(e) = app_store
|
||||||
.store_categories_for_user(id.as_str(), categories.as_str())
|
.store_categories_for_user(id.as_str(), categories.as_str())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e));
|
return api::Response::error(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
|
||||||
|
format!("{:?}", e),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(StatusCode::OK, "Successfully saved categories".to_owned())
|
api::Response::success("Successfully saved categories".into())
|
||||||
} else {
|
} else {
|
||||||
(
|
api::Response::Unauthorized
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
"You must be authorized to use this API call".to_owned(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,43 +168,31 @@ async fn api_save_recipes(
|
|||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
Json(recipes): Json<Vec<RecipeEntry>>,
|
Json(recipes): Json<Vec<RecipeEntry>>,
|
||||||
) -> impl IntoResponse {
|
) -> api::Response<()> {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
let result = app_store
|
let result = app_store
|
||||||
.store_recipes_for_user(id.as_str(), &recipes)
|
.store_recipes_for_user(id.as_str(), &recipes)
|
||||||
.await;
|
.await;
|
||||||
match result.map_err(|e| format!("Error: {:?}", e)) {
|
result.map_err(|e| format!("Error: {:?}", e)).into()
|
||||||
Ok(val) => Ok(axum::Json::from(val)),
|
|
||||||
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Err((
|
api::Response::Unauthorized
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
"You must be authorized to use this API call".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn api_plan(
|
async fn api_plan(
|
||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
) -> impl IntoResponse {
|
) -> api::PlanDataResponse {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
match app_store
|
app_store
|
||||||
.fetch_latest_meal_plan(&id)
|
.fetch_latest_meal_plan(&id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Error: {:?}", e))
|
.map_err(|e| format!("Error: {:?}", e))
|
||||||
{
|
.into()
|
||||||
Ok(val) => Ok(axum::Json::from(val)),
|
|
||||||
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Err((
|
api::Response::Unauthorized
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
"You must be authorized to use this API call".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,22 +200,16 @@ async fn api_plan_since(
|
|||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
Path(date): Path<chrono::NaiveDate>,
|
Path(date): Path<chrono::NaiveDate>,
|
||||||
) -> impl IntoResponse {
|
) -> api::Response<BTreeMap<NaiveDate, Vec<(String, i32)>>> {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
match app_store
|
app_store
|
||||||
.fetch_meal_plans_since(&id, date)
|
.fetch_meal_plans_since(&id, date)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Error: {:?}", e))
|
.map_err(|e| format!("Error: {:?}", e))
|
||||||
{
|
.into()
|
||||||
Ok(val) => Ok(axum::Json::from(val)),
|
|
||||||
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Err((
|
api::Response::Unauthorized
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
"You must be authorized to use this API call".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,63 +217,53 @@ async fn api_save_plan(
|
|||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
Json(meal_plan): Json<Vec<(String, i32)>>,
|
Json(meal_plan): Json<Vec<(String, i32)>>,
|
||||||
) -> impl IntoResponse {
|
) -> api::Response<()> {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
if let Err(e) = app_store
|
app_store
|
||||||
.save_meal_plan(id.as_str(), &meal_plan, chrono::Local::now().date_naive())
|
.save_meal_plan(id.as_str(), &meal_plan, chrono::Local::now().date_naive())
|
||||||
.await
|
.await
|
||||||
{
|
.map_err(|e| format!("{:?}", e))
|
||||||
return (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e));
|
.into()
|
||||||
}
|
|
||||||
(StatusCode::OK, "Successfully saved mealPlan".to_owned())
|
|
||||||
} else {
|
} else {
|
||||||
(
|
api::Response::Unauthorized
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
"You must be authorized to use this API call".to_owned(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn api_inventory_v2(
|
async fn api_inventory_v2(
|
||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
) -> impl IntoResponse {
|
) -> api::InventoryResponse {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
match app_store.fetch_latest_inventory_data(id).await {
|
app_store
|
||||||
Ok(tpl) => Ok(axum::Json::from(tpl)),
|
.fetch_latest_inventory_data(id)
|
||||||
Err(e) => {
|
.await
|
||||||
error!(err=?e);
|
.map_err(|e| format!("{:?}", e))
|
||||||
Err((StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e)))
|
.map(|d| {
|
||||||
}
|
let data: api::InventoryData = d.into();
|
||||||
}
|
data
|
||||||
|
})
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
Err((
|
api::Response::Unauthorized
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
"You must be authorized to use this API call".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn api_inventory(
|
async fn api_inventory(
|
||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
) -> impl IntoResponse {
|
) -> api::Response<(Vec<IngredientKey>, Vec<(IngredientKey, String)>)> {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
match app_store.fetch_latest_inventory_data(id).await {
|
app_store
|
||||||
Ok((item1, item2, _)) => Ok(axum::Json::from((item1, item2))),
|
.fetch_latest_inventory_data(id)
|
||||||
Err(e) => {
|
.await
|
||||||
error!(err=?e);
|
.map_err(|e| format!("{:?}", e))
|
||||||
Err((StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e)))
|
.map(|(filtered, modified, _)| (filtered, modified))
|
||||||
}
|
.into()
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Err((
|
api::Response::Unauthorized
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
"You must be authorized to use this API call".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,18 +273,12 @@ async fn save_inventory_data(
|
|||||||
filtered_ingredients: BTreeSet<IngredientKey>,
|
filtered_ingredients: BTreeSet<IngredientKey>,
|
||||||
modified_amts: BTreeMap<IngredientKey, String>,
|
modified_amts: BTreeMap<IngredientKey, String>,
|
||||||
extra_items: Vec<(String, String)>,
|
extra_items: Vec<(String, String)>,
|
||||||
) -> (StatusCode, String) {
|
) -> api::Response<()> {
|
||||||
if let Err(e) = app_store
|
app_store
|
||||||
.save_inventory_data(id, filtered_ingredients, modified_amts, extra_items)
|
.save_inventory_data(id, filtered_ingredients, modified_amts, extra_items)
|
||||||
.await
|
.await
|
||||||
{
|
.map_err(|e| format!("{:?}", e))
|
||||||
error!(err=?e);
|
.into()
|
||||||
return (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e));
|
|
||||||
}
|
|
||||||
(
|
|
||||||
StatusCode::OK,
|
|
||||||
"Successfully saved inventory data".to_owned(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn api_save_inventory_v2(
|
async fn api_save_inventory_v2(
|
||||||
@ -334,7 +289,7 @@ async fn api_save_inventory_v2(
|
|||||||
Vec<(IngredientKey, String)>,
|
Vec<(IngredientKey, String)>,
|
||||||
Vec<(String, String)>,
|
Vec<(String, String)>,
|
||||||
)>,
|
)>,
|
||||||
) -> impl IntoResponse {
|
) -> api::Response<()> {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
let filtered_ingredients = filtered_ingredients.into_iter().collect();
|
let filtered_ingredients = filtered_ingredients.into_iter().collect();
|
||||||
@ -347,11 +302,9 @@ async fn api_save_inventory_v2(
|
|||||||
extra_items,
|
extra_items,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
(
|
api::Response::Unauthorized
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
"You must be authorized to use this API call".to_owned(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,7 +315,7 @@ async fn api_save_inventory(
|
|||||||
Vec<IngredientKey>,
|
Vec<IngredientKey>,
|
||||||
Vec<(IngredientKey, String)>,
|
Vec<(IngredientKey, String)>,
|
||||||
)>,
|
)>,
|
||||||
) -> impl IntoResponse {
|
) -> api::Response<()> {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
let filtered_ingredients = filtered_ingredients.into_iter().collect();
|
let filtered_ingredients = filtered_ingredients.into_iter().collect();
|
||||||
@ -375,11 +328,9 @@ async fn api_save_inventory(
|
|||||||
Vec::new(),
|
Vec::new(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
(
|
api::Response::Unauthorized
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
"You must be authorized to use this API call".to_owned(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user