Various code improvements
This commit is contained in:
parent
d99b0344df
commit
ef6a1cbc9e
8 changed files with 745 additions and 206 deletions
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
use diesel::{
|
use diesel::{
|
||||||
r2d2::{ConnectionManager, PooledConnection},
|
r2d2::{ConnectionManager, PooledConnection},
|
||||||
|
result::QueryResult,
|
||||||
ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper,
|
ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper,
|
||||||
SqliteConnection,
|
SqliteConnection,
|
||||||
};
|
};
|
||||||
|
@ -24,12 +25,12 @@ use super::models::{DateEntry, Entry, FullDatabaseEntry, Heir, LocationEntry, Mu
|
||||||
|
|
||||||
type Connection<'a> = &'a mut PooledConnection<ConnectionManager<SqliteConnection>>;
|
type Connection<'a> = &'a mut PooledConnection<ConnectionManager<SqliteConnection>>;
|
||||||
|
|
||||||
pub fn user(user_id: &str, conn: Connection) -> diesel::result::QueryResult<User> {
|
pub fn user(user_id: &str, conn: Connection) -> QueryResult<User> {
|
||||||
use crate::database::schema::users::dsl::users;
|
use crate::database::schema::users::dsl::users;
|
||||||
users.find(user_id).select(User::as_select()).first(conn)
|
users.find(user_id).select(User::as_select()).first(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_by_email(email: &str, conn: Connection) -> diesel::result::QueryResult<Option<User>> {
|
pub fn user_by_email(email: &str, conn: Connection) -> QueryResult<Option<User>> {
|
||||||
use crate::database::schema::users::dsl as users;
|
use crate::database::schema::users::dsl as users;
|
||||||
users::users
|
users::users
|
||||||
.filter(users::email.eq(email))
|
.filter(users::email.eq(email))
|
||||||
|
@ -39,7 +40,7 @@ pub fn user_by_email(email: &str, conn: Connection) -> diesel::result::QueryResu
|
||||||
.optional()
|
.optional()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_heirs(user_id: &str, conn: Connection) -> diesel::result::QueryResult<Vec<Heir>> {
|
pub fn list_heirs(user_id: &str, conn: Connection) -> QueryResult<Vec<Heir>> {
|
||||||
use crate::database::schema::heirs::dsl as heirs;
|
use crate::database::schema::heirs::dsl as heirs;
|
||||||
heirs::heirs
|
heirs::heirs
|
||||||
.filter(heirs::user_id.eq(user_id))
|
.filter(heirs::user_id.eq(user_id))
|
||||||
|
@ -47,8 +48,42 @@ pub fn list_heirs(user_id: &str, conn: Connection) -> diesel::result::QueryResul
|
||||||
.load(conn)
|
.load(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_music_entry(music_entry: &MusicEntry, conn: Connection) -> QueryResult<()> {
|
||||||
|
use crate::database::schema::music_entries::dsl::*;
|
||||||
|
diesel::insert_into(music_entries)
|
||||||
|
.values((
|
||||||
|
id.eq(&music_entry.id),
|
||||||
|
artist.eq(&music_entry.artist),
|
||||||
|
title.eq(&music_entry.title),
|
||||||
|
links.eq(music_entry.links.to_string()),
|
||||||
|
universal_ids.eq(music_entry.universal_ids.to_string()),
|
||||||
|
))
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_location_entry(location_entry: &LocationEntry, conn: Connection) -> QueryResult<()> {
|
||||||
|
use crate::database::schema::location_entries::dsl::*;
|
||||||
|
diesel::insert_into(location_entries)
|
||||||
|
.values(location_entry)
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_date_entry(date_entry: &DateEntry, conn: Connection) -> QueryResult<()> {
|
||||||
|
use crate::database::schema::date_entries::dsl::*;
|
||||||
|
diesel::insert_into(date_entries)
|
||||||
|
.values(date_entry)
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! retrieve_sub_entry {
|
macro_rules! retrieve_sub_entry {
|
||||||
(($model:ident, $conn:ident) from $dsl:ident with id $id:expr) => {{
|
(($model:ident, $conn:ident) from $dsl:ident with id $id:expr) => {{
|
||||||
|
use $crate::database::schema::$dsl::dsl::$dsl;
|
||||||
let value = $id
|
let value = $id
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|id| $dsl.find(id).select($model::as_select()).first($conn));
|
.map(|id| $dsl.find(id).select($model::as_select()).first($conn));
|
||||||
|
@ -63,11 +98,8 @@ macro_rules! retrieve_sub_entry {
|
||||||
pub fn entry_recursive(
|
pub fn entry_recursive(
|
||||||
entry_id: &str,
|
entry_id: &str,
|
||||||
conn: Connection,
|
conn: Connection,
|
||||||
) -> diesel::result::QueryResult<FullDatabaseEntry> {
|
) -> QueryResult<FullDatabaseEntry> {
|
||||||
use crate::database::schema::date_entries::dsl::date_entries;
|
|
||||||
use crate::database::schema::entries::dsl::entries;
|
use crate::database::schema::entries::dsl::entries;
|
||||||
use crate::database::schema::location_entries::dsl::location_entries;
|
|
||||||
use crate::database::schema::music_entries::dsl::music_entries;
|
|
||||||
|
|
||||||
let entry: Entry = entries
|
let entry: Entry = entries
|
||||||
.find(entry_id)
|
.find(entry_id)
|
||||||
|
@ -83,7 +115,12 @@ pub fn entry_recursive(
|
||||||
Ok((entry, music_entry, location_entry, date_entry))
|
Ok((entry, music_entry, location_entry, date_entry))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_entries_recursive(user_id: &str, offset: i64, limit: i64, conn: Connection) -> diesel::result::QueryResult<Vec<FullDatabaseEntry>> {
|
pub fn list_entries_recursive(
|
||||||
|
user_id: &str,
|
||||||
|
offset: i64,
|
||||||
|
limit: i64,
|
||||||
|
conn: Connection,
|
||||||
|
) -> QueryResult<Vec<FullDatabaseEntry>> {
|
||||||
use crate::database::schema::entries::dsl as entries;
|
use crate::database::schema::entries::dsl as entries;
|
||||||
|
|
||||||
let entry_ids = entries::entries
|
let entry_ids = entries::entries
|
||||||
|
@ -92,5 +129,8 @@ pub fn list_entries_recursive(user_id: &str, offset: i64, limit: i64, conn: Conn
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.select(entries::id)
|
.select(entries::id)
|
||||||
.load::<String>(conn)?;
|
.load::<String>(conn)?;
|
||||||
entry_ids.iter().map(|id| entry_recursive(id, conn)).collect()
|
entry_ids
|
||||||
}
|
.iter()
|
||||||
|
.map(|id| entry_recursive(id, conn))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
220
identity-api-rs/src/http/entry.rs
Normal file
220
identity-api-rs/src/http/entry.rs
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
// Identity. Store your memories and mental belongings
|
||||||
|
// Copyright (C) 2024 Sofía Aritz
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use serde::de::Error as DeError;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::database::models::{
|
||||||
|
DateEntry, FullDatabaseEntry, LocationEntry, MusicEntry, UniversalId,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum HttpEntryFeeling {
|
||||||
|
Builtin(String),
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
Custom {
|
||||||
|
identifier: String,
|
||||||
|
description: String,
|
||||||
|
background_color: String,
|
||||||
|
text_color: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(sofia): Improve this impl
|
||||||
|
impl TryFrom<&str> for HttpEntryFeeling {
|
||||||
|
type Error = serde_json::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
if value.contains('\"') || value.contains('{') {
|
||||||
|
let json_value: serde_json::Value = serde_json::from_str(value)?;
|
||||||
|
let identifier = json_value
|
||||||
|
.get("identifier")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.ok_or_else(|| serde_json::Error::custom("Missing or invalid 'identifier' field"))?
|
||||||
|
.to_owned();
|
||||||
|
let description = json_value
|
||||||
|
.get("description")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.ok_or_else(|| serde_json::Error::custom("Missing or invalid 'description' field"))?
|
||||||
|
.to_owned();
|
||||||
|
let background_color = json_value
|
||||||
|
.get("background_color")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
serde_json::Error::custom("Missing or invalid 'background_color' field")
|
||||||
|
})?
|
||||||
|
.to_owned();
|
||||||
|
let text_color = json_value
|
||||||
|
.get("text_color")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.ok_or_else(|| serde_json::Error::custom("Missing or invalid 'text_color' field"))?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
Ok(Self::Custom {
|
||||||
|
identifier,
|
||||||
|
description,
|
||||||
|
background_color,
|
||||||
|
text_color,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Self::Builtin(value.to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum HttpEntryLocation {
|
||||||
|
Description(String),
|
||||||
|
Exact { latitude: f64, longitude: f64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||||
|
#[serde(tag = "kind")]
|
||||||
|
pub enum HttpEntryBase {
|
||||||
|
#[serde(rename = "event")]
|
||||||
|
Event,
|
||||||
|
#[serde(rename = "memory")]
|
||||||
|
Memory,
|
||||||
|
#[serde(rename = "feeling")]
|
||||||
|
Feeling,
|
||||||
|
#[serde(rename = "environment")]
|
||||||
|
Environment { location: Option<HttpEntryLocation> },
|
||||||
|
#[serde(rename = "date")]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
Date { referenced_date: String },
|
||||||
|
#[serde(rename = "song")]
|
||||||
|
Song {
|
||||||
|
artist: String,
|
||||||
|
title: String,
|
||||||
|
links: Vec<String>,
|
||||||
|
id: Vec<UniversalId>,
|
||||||
|
},
|
||||||
|
#[serde(rename = "album")]
|
||||||
|
Album {
|
||||||
|
artist: String,
|
||||||
|
title: String,
|
||||||
|
links: Vec<String>,
|
||||||
|
id: Vec<UniversalId>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpEntryBase {
|
||||||
|
pub fn kind(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Event => "event",
|
||||||
|
Self::Memory => "memory",
|
||||||
|
Self::Feeling => "feeling",
|
||||||
|
Self::Environment { .. } => "environment",
|
||||||
|
Self::Date { .. } => "date",
|
||||||
|
Self::Song { .. } => "song",
|
||||||
|
Self::Album { .. } => "album",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_kind(
|
||||||
|
kind: &str,
|
||||||
|
(music_entry, location_entry, date_entry): (
|
||||||
|
Option<MusicEntry>,
|
||||||
|
Option<LocationEntry>,
|
||||||
|
Option<DateEntry>,
|
||||||
|
),
|
||||||
|
) -> Option<Self> {
|
||||||
|
match kind {
|
||||||
|
"event" => Some(Self::Event),
|
||||||
|
"memory" => Some(Self::Memory),
|
||||||
|
"feeling" => Some(Self::Feeling),
|
||||||
|
"environment" => Some(Self::Environment {
|
||||||
|
location: location_entry.map(|v| {
|
||||||
|
if let Some(text) = v.location_text {
|
||||||
|
HttpEntryLocation::Description(text)
|
||||||
|
} else {
|
||||||
|
let coords = v.location_coordinates().unwrap();
|
||||||
|
HttpEntryLocation::Exact {
|
||||||
|
latitude: coords.latitude,
|
||||||
|
longitude: coords.longitude,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
"date" => Some(Self::Date {
|
||||||
|
referenced_date: date_entry.unwrap().referenced_date.to_string(),
|
||||||
|
}),
|
||||||
|
"song" => {
|
||||||
|
let music_entry = music_entry.unwrap();
|
||||||
|
Some(Self::Song {
|
||||||
|
artist: music_entry.artist,
|
||||||
|
title: music_entry.title,
|
||||||
|
links: music_entry.links.0,
|
||||||
|
id: music_entry.universal_ids.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"album" => {
|
||||||
|
let music_entry = music_entry.unwrap();
|
||||||
|
Some(Self::Album {
|
||||||
|
artist: music_entry.artist,
|
||||||
|
title: music_entry.title,
|
||||||
|
links: music_entry.links.0,
|
||||||
|
id: music_entry.universal_ids.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct HttpEntry {
|
||||||
|
/// Only `Some` when built by the server
|
||||||
|
pub id: Option<String>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub creation_date: String,
|
||||||
|
pub assets: Vec<String>,
|
||||||
|
pub feelings: Vec<HttpEntryFeeling>,
|
||||||
|
pub base: HttpEntryBase,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<FullDatabaseEntry> for HttpEntry {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(
|
||||||
|
(entry, music_entry, location_entry, date_entry): FullDatabaseEntry,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(base) =
|
||||||
|
HttpEntryBase::from_kind(&entry.kind, (music_entry, location_entry, date_entry))
|
||||||
|
{
|
||||||
|
Ok(Self {
|
||||||
|
id: Some(entry.id),
|
||||||
|
title: entry.title,
|
||||||
|
description: entry.description,
|
||||||
|
creation_date: entry.created_at.to_string(),
|
||||||
|
assets: entry.assets.0,
|
||||||
|
feelings: entry
|
||||||
|
.feelings
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter_map(|v| v.as_str().try_into().ok())
|
||||||
|
.collect(),
|
||||||
|
base,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("invalid data stored in the database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,9 @@ use axum::{
|
||||||
http::{header::AUTHORIZATION, request::Parts, StatusCode},
|
http::{header::AUTHORIZATION, request::Parts, StatusCode},
|
||||||
};
|
};
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
|
use super::database::Database;
|
||||||
|
|
||||||
pub struct ExtractJwtUser(pub JwtUser);
|
pub struct ExtractJwtUser(pub JwtUser);
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -64,17 +67,13 @@ impl FromRequestParts<AppState> for ExtractUser {
|
||||||
parts: &mut Parts,
|
parts: &mut Parts,
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
) -> Result<Self, Self::Rejection> {
|
) -> Result<Self, Self::Rejection> {
|
||||||
let jwt_user = ExtractJwtUser::from_request_parts(parts, state).await?;
|
let ExtractJwtUser(jwt_user) = ExtractJwtUser::from_request_parts(parts, state).await?;
|
||||||
|
let Database(mut conn) = Database::from_request_parts(parts, state).await?;
|
||||||
|
|
||||||
if let Ok(mut conn) = state.pool.get() {
|
if let Ok(user) = actions::user(&jwt_user.uid, &mut conn) {
|
||||||
if let Ok(user) = actions::user(&jwt_user.0.uid, &mut conn) {
|
Ok(Self(user))
|
||||||
Ok(Self(user))
|
|
||||||
} else {
|
|
||||||
error!("JWT user does not exist in database");
|
|
||||||
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
error!("failed to obtain pooled connection");
|
error!("JWT user does not exist in database");
|
||||||
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
|
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
30
identity-api-rs/src/http/extractors/database.rs
Normal file
30
identity-api-rs/src/http/extractors/database.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use crate::AppState;
|
||||||
|
use axum::{
|
||||||
|
async_trait,
|
||||||
|
extract::FromRequestParts,
|
||||||
|
http::{header::AUTHORIZATION, request::Parts, StatusCode},
|
||||||
|
};
|
||||||
|
use diesel::{
|
||||||
|
r2d2::{ConnectionManager, PooledConnection},
|
||||||
|
SqliteConnection,
|
||||||
|
};
|
||||||
|
use tracing::{error, warn};
|
||||||
|
|
||||||
|
pub struct Database(pub PooledConnection<ConnectionManager<SqliteConnection>>);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FromRequestParts<AppState> for Database {
|
||||||
|
type Rejection = (StatusCode, &'static str);
|
||||||
|
|
||||||
|
async fn from_request_parts(
|
||||||
|
_parts: &mut Parts,
|
||||||
|
state: &AppState,
|
||||||
|
) -> Result<Self, Self::Rejection> {
|
||||||
|
let conn = state.pool.get().map_err(|err| {
|
||||||
|
error!("failed to obtain pooled connection: {:?}", err);
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self(conn))
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,3 +15,4 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod database;
|
||||||
|
|
|
@ -17,7 +17,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{encode_jwt, expiration_time, JwtUser},
|
auth::{encode_jwt, expiration_time, JwtUser},
|
||||||
database::actions,
|
database::actions,
|
||||||
http::extractors::auth::{ExtractJwtUser, ExtractUser},
|
http::extractors::{
|
||||||
|
auth::{ExtractJwtUser, ExtractUser},
|
||||||
|
database::Database,
|
||||||
|
},
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
use argon2::{
|
use argon2::{
|
||||||
|
@ -25,7 +28,6 @@ use argon2::{
|
||||||
Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
|
Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
routing::{delete, get, post, put},
|
routing::{delete, get, post, put},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
|
@ -72,29 +74,24 @@ struct GenkeyResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn genkey(
|
async fn genkey(
|
||||||
State(state): State<AppState>,
|
Database(mut conn): Database,
|
||||||
ExtractJwtUser(user): ExtractJwtUser,
|
ExtractJwtUser(user): ExtractJwtUser,
|
||||||
) -> Result<Json<GenkeyResponse>, StatusCode> {
|
) -> Result<Json<GenkeyResponse>, StatusCode> {
|
||||||
use crate::database::schema::session_keys::dsl::*;
|
use crate::database::schema::session_keys::dsl::*;
|
||||||
|
|
||||||
if let Ok(mut conn) = state.pool.get() {
|
let session_key = Uuid::new_v4().to_string();
|
||||||
let session_key = Uuid::new_v4().to_string();
|
let result = diesel::insert_into(session_keys)
|
||||||
let result = diesel::insert_into(session_keys)
|
.values((user_id.eq(&user.uid), key.eq(&session_key)))
|
||||||
.values((user_id.eq(&user.uid), key.eq(&session_key)))
|
.execute(&mut conn);
|
||||||
.execute(&mut conn);
|
|
||||||
|
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
Ok(Json(GenkeyResponse { session_key }))
|
Ok(Json(GenkeyResponse { session_key }))
|
||||||
} else {
|
|
||||||
error!(
|
|
||||||
"failed to insert into session_keys {}, error: {:?}",
|
|
||||||
user.uid,
|
|
||||||
result.err()
|
|
||||||
);
|
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
error!("failed to obtain pooled connection");
|
error!(
|
||||||
|
"failed to insert into session_keys {}, error: {:?}",
|
||||||
|
user.uid,
|
||||||
|
result.err()
|
||||||
|
);
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,41 +108,35 @@ struct LoginResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn login(
|
async fn login(
|
||||||
State(state): State<AppState>,
|
Database(mut conn): Database,
|
||||||
Json(req): Json<LoginRequest>,
|
Json(req): Json<LoginRequest>,
|
||||||
) -> Result<Json<LoginResponse>, StatusCode> {
|
) -> Result<Json<LoginResponse>, StatusCode> {
|
||||||
if let Ok(mut conn) = state.pool.get() {
|
if let Ok(Some(user)) = actions::user_by_email(&req.email, &mut conn) {
|
||||||
if let Ok(Some(user)) = actions::user_by_email(&req.email, &mut conn) {
|
let parsed_hash = PasswordHash::new(&user.password).expect("invalid argon2 password hash");
|
||||||
let parsed_hash =
|
if Argon2::default()
|
||||||
PasswordHash::new(&user.password).expect("invalid argon2 password hash");
|
.verify_password(req.password.as_bytes(), &parsed_hash)
|
||||||
if Argon2::default()
|
.is_err()
|
||||||
.verify_password(req.password.as_bytes(), &parsed_hash)
|
{
|
||||||
.is_err()
|
info!("failed login attempt, invalid password: {}", &req.email);
|
||||||
{
|
Err(StatusCode::UNAUTHORIZED)
|
||||||
info!("failed login attempt, invalid password: {}", &req.email);
|
} else {
|
||||||
Err(StatusCode::UNAUTHORIZED)
|
info!("valid login attempt: {}", req.email);
|
||||||
} else {
|
match encode_jwt(&JwtUser {
|
||||||
info!("valid login attempt: {}", req.email);
|
uid: user.id,
|
||||||
match encode_jwt(&JwtUser {
|
email: user.email,
|
||||||
uid: user.id,
|
name: user.name,
|
||||||
email: user.email,
|
exp: expiration_time(),
|
||||||
name: user.name,
|
}) {
|
||||||
exp: expiration_time(),
|
Ok(token) => Ok(Json(LoginResponse { token })),
|
||||||
}) {
|
Err(err) => {
|
||||||
Ok(token) => Ok(Json(LoginResponse { token })),
|
error!("token couldn't be encoded: {:?}", err);
|
||||||
Err(err) => {
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
error!("token couldn't be encoded: {:?}", err);
|
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
info!("failed login attempt, email does not exist: {}", &req.email);
|
|
||||||
Err(StatusCode::UNAUTHORIZED)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("failed to obtain pooled connection");
|
info!("failed login attempt, email does not exist: {}", &req.email);
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
Err(StatusCode::UNAUTHORIZED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,94 +153,89 @@ struct RegisterResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn register(
|
async fn register(
|
||||||
State(state): State<AppState>,
|
Database(mut conn): Database,
|
||||||
Json(req): Json<RegisterRequest>,
|
Json(req): Json<RegisterRequest>,
|
||||||
) -> Result<Json<RegisterResponse>, StatusCode> {
|
) -> Result<Json<RegisterResponse>, StatusCode> {
|
||||||
use crate::database::schema::limits::dsl as limits;
|
use crate::database::schema::limits::dsl as limits;
|
||||||
use crate::database::schema::users::dsl as users;
|
use crate::database::schema::users::dsl as users;
|
||||||
|
|
||||||
if let Ok(mut conn) = state.pool.get() {
|
let user = actions::user_by_email(&req.email, &mut conn);
|
||||||
let user = actions::user_by_email(&req.email, &mut conn);
|
|
||||||
|
|
||||||
if user.is_err() {
|
if user.is_err() {
|
||||||
error!(
|
error!(
|
||||||
"failed to retrieve potential existing user from database: {}, error: {:?}",
|
"failed to retrieve potential existing user from database: {}, error: {:?}",
|
||||||
&req.email,
|
&req.email,
|
||||||
user.err()
|
user.err()
|
||||||
);
|
);
|
||||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.is_ok_and(|v| v.is_some()) {
|
if user.is_ok_and(|v| v.is_some()) {
|
||||||
info!("tried to register existing user: {}", &req.email);
|
info!("tried to register existing user: {}", &req.email);
|
||||||
return Err(StatusCode::BAD_REQUEST);
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
let limit_id = Uuid::new_v4().to_string();
|
let limit_id = Uuid::new_v4().to_string();
|
||||||
let result = diesel::insert_into(limits::limits)
|
let result = diesel::insert_into(limits::limits)
|
||||||
|
.values((
|
||||||
|
limits::id.eq(&limit_id),
|
||||||
|
limits::current_asset_count.eq(0),
|
||||||
|
limits::max_asset_count.eq(10),
|
||||||
|
))
|
||||||
|
.execute(&mut conn);
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
error!(
|
||||||
|
"failed to insert into limits: {}, error: {:?}",
|
||||||
|
&req.email,
|
||||||
|
result.err()
|
||||||
|
);
|
||||||
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
let password_hash = argon2.hash_password(req.password.as_bytes(), &salt);
|
||||||
|
|
||||||
|
if let Ok(password_hash) = password_hash {
|
||||||
|
let user_id = Uuid::new_v4().to_string();
|
||||||
|
let result = diesel::insert_into(users::users)
|
||||||
.values((
|
.values((
|
||||||
limits::id.eq(&limit_id),
|
users::id.eq(&user_id),
|
||||||
limits::current_asset_count.eq(0),
|
users::created_at.eq(Utc::now().naive_utc()),
|
||||||
limits::max_asset_count.eq(10),
|
users::last_connected_at.eq(Utc::now().naive_utc()),
|
||||||
|
users::email.eq(&req.email),
|
||||||
|
users::password.eq(password_hash.to_string()),
|
||||||
|
users::name.eq(&req.name),
|
||||||
|
users::limits.eq(&limit_id),
|
||||||
|
// FIXME(sofia): Implement diesel::Expression for List
|
||||||
|
users::assets.eq("[]"),
|
||||||
))
|
))
|
||||||
.execute(&mut conn);
|
.execute(&mut conn);
|
||||||
|
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
error!(
|
error!(
|
||||||
"failed to insert into limits: {}, error: {:?}",
|
"failed to insert into users: {}, error: {:?}",
|
||||||
&req.email,
|
req.email,
|
||||||
result.err()
|
result.err()
|
||||||
);
|
);
|
||||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
match crate::auth::encode_jwt(&JwtUser {
|
||||||
let argon2 = Argon2::default();
|
uid: user_id,
|
||||||
let password_hash = argon2.hash_password(req.password.as_bytes(), &salt);
|
email: req.email,
|
||||||
|
name: req.name,
|
||||||
if let Ok(password_hash) = password_hash {
|
exp: expiration_time(),
|
||||||
let user_id = Uuid::new_v4().to_string();
|
}) {
|
||||||
let result = diesel::insert_into(users::users)
|
Ok(token) => Ok(Json(RegisterResponse { token })),
|
||||||
.values((
|
Err(err) => {
|
||||||
users::id.eq(&user_id),
|
error!("token couldn't be encoded: {:?}", err);
|
||||||
users::created_at.eq(Utc::now().naive_utc()),
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
users::last_connected_at.eq(Utc::now().naive_utc()),
|
|
||||||
users::email.eq(&req.email),
|
|
||||||
users::password.eq(password_hash.to_string()),
|
|
||||||
users::name.eq(&req.name),
|
|
||||||
users::limits.eq(&limit_id),
|
|
||||||
// FIXME(sofia): Implement diesel::Expression for List
|
|
||||||
users::assets.eq("[]"),
|
|
||||||
))
|
|
||||||
.execute(&mut conn);
|
|
||||||
|
|
||||||
if result.is_err() {
|
|
||||||
error!(
|
|
||||||
"failed to insert into users: {}, error: {:?}",
|
|
||||||
req.email,
|
|
||||||
result.err()
|
|
||||||
);
|
|
||||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match crate::auth::encode_jwt(&JwtUser {
|
|
||||||
uid: user_id,
|
|
||||||
email: req.email,
|
|
||||||
name: req.name,
|
|
||||||
exp: expiration_time(),
|
|
||||||
}) {
|
|
||||||
Ok(token) => Ok(Json(RegisterResponse { token })),
|
|
||||||
Err(err) => {
|
|
||||||
error!("token couldn't be encoded: {:?}", err);
|
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!("failed to hash password: {:?}", password_hash.err());
|
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("failed to obtain pooled connection");
|
error!("failed to hash password: {:?}", password_hash.err());
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,23 +262,18 @@ impl From<crate::database::models::Heir> for HttpHeir {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_heirs(
|
async fn list_heirs(
|
||||||
State(state): State<AppState>,
|
Database(mut conn): Database,
|
||||||
ExtractJwtUser(user): ExtractJwtUser,
|
ExtractJwtUser(user): ExtractJwtUser,
|
||||||
) -> Result<Json<Vec<HttpHeir>>, StatusCode> {
|
) -> Result<Json<Vec<HttpHeir>>, StatusCode> {
|
||||||
if let Ok(mut conn) = state.pool.get() {
|
let result = actions::list_heirs(&user.uid, &mut conn);
|
||||||
let result = actions::list_heirs(&user.uid, &mut conn);
|
if let Ok(heirs) = result {
|
||||||
if let Ok(heirs) = result {
|
Ok(Json(heirs.into_iter().map(HttpHeir::from).collect()))
|
||||||
Ok(Json(heirs.into_iter().map(HttpHeir::from).collect()))
|
|
||||||
} else {
|
|
||||||
error!(
|
|
||||||
"failed to obtain heirs: {}, error: {:?}",
|
|
||||||
user.uid,
|
|
||||||
result.err()
|
|
||||||
);
|
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
error!("failed to obtain pooled connection");
|
error!(
|
||||||
|
"failed to obtain heirs: {}, error: {:?}",
|
||||||
|
user.uid,
|
||||||
|
result.err()
|
||||||
|
);
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,46 +287,42 @@ struct InsertHeirRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn insert_heir(
|
async fn insert_heir(
|
||||||
State(state): State<AppState>,
|
Database(mut conn): Database,
|
||||||
ExtractJwtUser(user): ExtractJwtUser,
|
ExtractJwtUser(user): ExtractJwtUser,
|
||||||
Json(req): Json<InsertHeirRequest>,
|
Json(req): Json<InsertHeirRequest>,
|
||||||
) -> Result<Json<Vec<HttpHeir>>, StatusCode> {
|
) -> Result<Json<Vec<HttpHeir>>, StatusCode> {
|
||||||
use crate::database::schema::heirs::dsl::*;
|
use crate::database::schema::heirs::dsl::*;
|
||||||
if let Ok(mut conn) = state.pool.get() {
|
|
||||||
let heir_id = Uuid::new_v4().to_string();
|
|
||||||
let result = diesel::insert_into(heirs)
|
|
||||||
.values((
|
|
||||||
id.eq(heir_id),
|
|
||||||
created_at.eq(Utc::now().naive_utc()),
|
|
||||||
user_id.eq(&user.uid),
|
|
||||||
name.eq(req.name),
|
|
||||||
// Only e-mail is implemented right now
|
|
||||||
email.eq(req.value),
|
|
||||||
))
|
|
||||||
.execute(&mut conn);
|
|
||||||
|
|
||||||
if result.is_err() {
|
let heir_id = Uuid::new_v4().to_string();
|
||||||
error!(
|
let result = diesel::insert_into(heirs)
|
||||||
"failed to insert into heirs: {}, error: {:?}",
|
.values((
|
||||||
user.uid,
|
id.eq(heir_id),
|
||||||
result.err()
|
created_at.eq(Utc::now().naive_utc()),
|
||||||
);
|
user_id.eq(&user.uid),
|
||||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
name.eq(req.name),
|
||||||
}
|
// Only e-mail is implemented right now
|
||||||
|
email.eq(req.value),
|
||||||
|
))
|
||||||
|
.execute(&mut conn);
|
||||||
|
|
||||||
let result = actions::list_heirs(&user.uid, &mut conn);
|
if result.is_err() {
|
||||||
if let Ok(heirs_list) = result {
|
error!(
|
||||||
Ok(Json(heirs_list.into_iter().map(HttpHeir::from).collect()))
|
"failed to insert into heirs: {}, error: {:?}",
|
||||||
} else {
|
user.uid,
|
||||||
error!(
|
result.err()
|
||||||
"failed to obtain heirs: {}, error: {:?}",
|
);
|
||||||
user.uid,
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
result.err()
|
}
|
||||||
);
|
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
let result = actions::list_heirs(&user.uid, &mut conn);
|
||||||
}
|
if let Ok(heirs_list) = result {
|
||||||
|
Ok(Json(heirs_list.into_iter().map(HttpHeir::from).collect()))
|
||||||
} else {
|
} else {
|
||||||
error!("failed to obtain pooled connection");
|
error!(
|
||||||
|
"failed to obtain heirs: {}, error: {:?}",
|
||||||
|
user.uid,
|
||||||
|
result.err()
|
||||||
|
);
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,36 +333,32 @@ struct DeleteHeirRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_heir(
|
async fn delete_heir(
|
||||||
State(state): State<AppState>,
|
Database(mut conn): Database,
|
||||||
ExtractJwtUser(user): ExtractJwtUser,
|
ExtractJwtUser(user): ExtractJwtUser,
|
||||||
Json(req): Json<DeleteHeirRequest>,
|
Json(req): Json<DeleteHeirRequest>,
|
||||||
) -> Result<Json<Vec<HttpHeir>>, StatusCode> {
|
) -> Result<Json<Vec<HttpHeir>>, StatusCode> {
|
||||||
use crate::database::schema::heirs::dsl::*;
|
use crate::database::schema::heirs::dsl::*;
|
||||||
if let Ok(mut conn) = state.pool.get() {
|
|
||||||
let result = diesel::delete(heirs.filter(id.eq(&req.id))).execute(&mut conn);
|
|
||||||
if result.is_err() {
|
|
||||||
error!(
|
|
||||||
"failed to delete from heirs: {}, heir_id: {}, error: {:?}",
|
|
||||||
user.uid,
|
|
||||||
req.id,
|
|
||||||
result.err()
|
|
||||||
);
|
|
||||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = actions::list_heirs(&user.uid, &mut conn);
|
let result = diesel::delete(heirs.filter(id.eq(&req.id))).execute(&mut conn);
|
||||||
if let Ok(heirs_list) = result {
|
if result.is_err() {
|
||||||
Ok(Json(heirs_list.into_iter().map(HttpHeir::from).collect()))
|
error!(
|
||||||
} else {
|
"failed to delete from heirs: {}, heir_id: {}, error: {:?}",
|
||||||
error!(
|
user.uid,
|
||||||
"failed to obtain heirs: {}, error: {:?}",
|
req.id,
|
||||||
user.uid,
|
result.err()
|
||||||
result.err()
|
);
|
||||||
);
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
}
|
||||||
}
|
|
||||||
|
let result = actions::list_heirs(&user.uid, &mut conn);
|
||||||
|
if let Ok(heirs_list) = result {
|
||||||
|
Ok(Json(heirs_list.into_iter().map(HttpHeir::from).collect()))
|
||||||
} else {
|
} else {
|
||||||
error!("failed to obtain pooled connection");
|
error!(
|
||||||
|
"failed to obtain heirs: {}, error: {:?}",
|
||||||
|
user.uid,
|
||||||
|
result.err()
|
||||||
|
);
|
||||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
265
identity-api-rs/src/http/routes/entry.rs
Normal file
265
identity-api-rs/src/http/routes/entry.rs
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
// Identity. Store your memories and mental belongings
|
||||||
|
// Copyright (C) 2024 Sofía Aritz
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published
|
||||||
|
// by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
database::{
|
||||||
|
actions,
|
||||||
|
list::List,
|
||||||
|
models::{DateEntry, LocationEntry, MusicEntry},
|
||||||
|
},
|
||||||
|
http::{
|
||||||
|
entry::*,
|
||||||
|
extractors::{
|
||||||
|
auth::{ExtractJwtUser, ExtractUser},
|
||||||
|
database::Database,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppState,
|
||||||
|
};
|
||||||
|
use axum::{
|
||||||
|
extract::{Query, State},
|
||||||
|
http::StatusCode,
|
||||||
|
routing::{delete, get, put},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use chrono::{NaiveDate, NaiveDateTime, NaiveTime, Utc};
|
||||||
|
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
|
use tracing::{error, info, warn};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub fn entry_router() -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
|
.route("/entry", delete(delete_entry))
|
||||||
|
.route("/entry", put(insert_entry))
|
||||||
|
.route("/entry/list", get(list_entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct DeleteEntryQuery {
|
||||||
|
entry_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(sofia): Error on non existent entry_id
|
||||||
|
async fn delete_entry(
|
||||||
|
Database(mut conn): Database,
|
||||||
|
Query(query): Query<DeleteEntryQuery>,
|
||||||
|
ExtractJwtUser(user): ExtractJwtUser,
|
||||||
|
) -> Result<(), StatusCode> {
|
||||||
|
use crate::database::schema::entries::dsl::*;
|
||||||
|
|
||||||
|
if let Err(err) = diesel::delete(entries.filter(id.eq(&query.entry_id))).execute(&mut conn) {
|
||||||
|
error!(
|
||||||
|
"failed to delete from heirs: {}, entry_id: {}, error: {:?}",
|
||||||
|
user.uid, query.entry_id, err
|
||||||
|
);
|
||||||
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("deleted entry {}", query.entry_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ListEntriesQuery {
|
||||||
|
offset: i64,
|
||||||
|
limit: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_entries(
|
||||||
|
Database(mut conn): Database,
|
||||||
|
Query(query): Query<ListEntriesQuery>,
|
||||||
|
ExtractUser(user): ExtractUser,
|
||||||
|
) -> Result<Json<Vec<HttpEntry>>, StatusCode> {
|
||||||
|
let result = actions::list_entries_recursive(&user.id, query.offset, query.limit, &mut conn);
|
||||||
|
if let Ok(entries) = result {
|
||||||
|
Ok(Json(
|
||||||
|
entries
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|v| HttpEntry::try_from(v).ok())
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
error!("failed to obtain entries {}: {:?}", user.id, result.err());
|
||||||
|
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct InsertEntryBody {
|
||||||
|
entry: HttpEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_entry(
|
||||||
|
Database(mut conn): Database,
|
||||||
|
ExtractUser(user): ExtractUser,
|
||||||
|
Json(entry): Json<InsertEntryBody>,
|
||||||
|
) -> Result<(), StatusCode> {
|
||||||
|
let mut music_entry: Option<MusicEntry> = None;
|
||||||
|
let mut location_entry: Option<LocationEntry> = None;
|
||||||
|
let mut date_entry: Option<DateEntry> = None;
|
||||||
|
|
||||||
|
let entry = entry.entry;
|
||||||
|
match entry.base {
|
||||||
|
HttpEntryBase::Album { ref artist, ref title, ref links, ref id }
|
||||||
|
| HttpEntryBase::Song { ref artist, ref title, ref links, ref id } => {
|
||||||
|
music_entry = Some(MusicEntry {
|
||||||
|
id: Uuid::new_v4().to_string(),
|
||||||
|
// FIXME(sofia): These clones seems unnecesary
|
||||||
|
title: title.to_owned(),
|
||||||
|
links: links.clone().into(),
|
||||||
|
artist: artist.clone(),
|
||||||
|
universal_ids: id.clone().into(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
HttpEntryBase::Environment { ref location } => {
|
||||||
|
if entry.title.as_ref().is_none_or(|v| v.is_empty()) {
|
||||||
|
warn!(
|
||||||
|
"no title in request for inserting environment entry: {}",
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(location) = location {
|
||||||
|
match location {
|
||||||
|
HttpEntryLocation::Description(description) => {
|
||||||
|
location_entry = Some(LocationEntry {
|
||||||
|
id: Uuid::new_v4().to_string(),
|
||||||
|
// FIXME(sofia): This clone seems unnecesary
|
||||||
|
location_text: Some(description.clone()),
|
||||||
|
location_coordinates: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
HttpEntryLocation::Exact {
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
} => {
|
||||||
|
location_entry = Some(LocationEntry {
|
||||||
|
id: Uuid::new_v4().to_string(),
|
||||||
|
location_text: None,
|
||||||
|
location_coordinates: Some(
|
||||||
|
json!({
|
||||||
|
"latitude": latitude,
|
||||||
|
"longitude": longitude,
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpEntryBase::Date {
|
||||||
|
ref referenced_date,
|
||||||
|
} => {
|
||||||
|
let naive_date = NaiveDate::parse_from_str(referenced_date, "%Y-%m-%d");
|
||||||
|
if let Err(err) = naive_date {
|
||||||
|
warn!(
|
||||||
|
"invalid date in request for inserting entry: {}, err: {err:?}",
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
date_entry = Some(DateEntry {
|
||||||
|
id: Uuid::new_v4().to_string(),
|
||||||
|
referenced_date: NaiveDateTime::new(
|
||||||
|
naive_date.unwrap(),
|
||||||
|
NaiveTime::from_hms_milli_opt(0, 0, 0, 0).unwrap(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
HttpEntryBase::Event => {
|
||||||
|
if entry.description.as_ref().is_none_or(|v| v.is_empty()) {
|
||||||
|
warn!(
|
||||||
|
"no description or title in request for inserting event entry: {}",
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpEntryBase::Memory => {
|
||||||
|
if entry.description.as_ref().is_none_or(|v| v.is_empty())
|
||||||
|
|| entry.title.as_ref().is_none_or(|v| v.is_empty())
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
"no description or title in request for inserting memory entry: {}",
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HttpEntryBase::Feeling => {
|
||||||
|
if entry.feelings.is_empty() {
|
||||||
|
warn!(
|
||||||
|
"no feelings in request for inserting feeling entry: {}",
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let music_entry_id = music_entry.as_ref().map(|v| v.id.clone());
|
||||||
|
music_entry.map(|music_entry| actions::insert_music_entry(&music_entry, &mut conn).map_err(|err| {
|
||||||
|
error!("failed to insert into music_entries: {}, error: {err:?}",user.id);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})).transpose()?;
|
||||||
|
|
||||||
|
let location_entry_id = location_entry.as_ref().map(|v| v.id.clone());
|
||||||
|
location_entry.map(|location_entry| actions::insert_location_entry(&location_entry, &mut conn).map_err(|err| {
|
||||||
|
error!("failed to insert into location_entries: {}, error: {err:?}",user.id);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})).transpose()?;
|
||||||
|
|
||||||
|
let date_entry_id = date_entry.as_ref().map(|v| v.id.clone());
|
||||||
|
date_entry.map(|date_entry| actions::insert_date_entry(&date_entry, &mut conn).map_err(|err| {
|
||||||
|
error!("failed to insert into date_entries: {}, error: {err:?}",user.id);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})).transpose()?;
|
||||||
|
|
||||||
|
{
|
||||||
|
use crate::database::schema::entries::dsl as entries;
|
||||||
|
let result = diesel::insert_into(entries::entries)
|
||||||
|
.values((
|
||||||
|
entries::id.eq(Uuid::new_v4().to_string()),
|
||||||
|
entries::user_id.eq(&user.id),
|
||||||
|
entries::created_at.eq(Utc::now().naive_utc()),
|
||||||
|
entries::feelings.eq(List::from(entry.feelings).to_string()),
|
||||||
|
// FIXME(sofia): Check that the assets exists
|
||||||
|
entries::assets.eq(List::from(entry.assets).to_string()),
|
||||||
|
entries::title.eq(&entry.title),
|
||||||
|
entries::description.eq(&entry.description),
|
||||||
|
entries::kind.eq(&entry.base.kind()),
|
||||||
|
entries::date_entry.eq(date_entry_id),
|
||||||
|
entries::music_entry.eq(music_entry_id),
|
||||||
|
entries::location_entry.eq(location_entry_id),
|
||||||
|
))
|
||||||
|
.execute(&mut conn);
|
||||||
|
|
||||||
|
if let Err(err) = result {
|
||||||
|
error!(
|
||||||
|
"failed to insert into entries: {}, error: {:?}",
|
||||||
|
user.id, err
|
||||||
|
);
|
||||||
|
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -15,7 +15,14 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{MatchedPath, Request}, http::{header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}, Method}, response::Response, routing::get, Router
|
extract::{MatchedPath, Request},
|
||||||
|
http::{
|
||||||
|
header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE},
|
||||||
|
Method,
|
||||||
|
},
|
||||||
|
response::Response,
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
};
|
};
|
||||||
use database::create_connection_pool;
|
use database::create_connection_pool;
|
||||||
use diesel::{r2d2::ConnectionManager, SqliteConnection};
|
use diesel::{r2d2::ConnectionManager, SqliteConnection};
|
||||||
|
@ -23,7 +30,11 @@ use env::{listen_port, LoadEnvError};
|
||||||
use http::routes::{auth::auth_router, entry::entry_router};
|
use http::routes::{auth::auth_router, entry::entry_router};
|
||||||
use r2d2::Pool;
|
use r2d2::Pool;
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer, cors::{Any, CorsLayer}};
|
use tower_http::{
|
||||||
|
classify::ServerErrorsFailureClass,
|
||||||
|
cors::{Any, CorsLayer},
|
||||||
|
trace::TraceLayer,
|
||||||
|
};
|
||||||
use tracing::{error, info, info_span, warn, Span};
|
use tracing::{error, info, info_span, warn, Span};
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue