wip
This commit is contained in:
@@ -3,17 +3,17 @@ use leptos::prelude::*;
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn ProblemInfo(problem: models::Problem) -> impl IntoView {
|
pub fn ProblemInfo(#[prop(into)] problem: Signal<models::Problem>) -> impl IntoView {
|
||||||
tracing::trace!("Enter problem info");
|
tracing::trace!("Enter problem info");
|
||||||
|
|
||||||
let name = problem.name;
|
let name = Signal::derive(move || problem.read().name.clone());
|
||||||
let set_by = problem.set_by;
|
let set_by = Signal::derive(move || problem.read().set_by.clone());
|
||||||
let method = problem.method;
|
let method = Signal::derive(move || problem.read().method.to_string());
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="grid grid-rows-none gap-y-1 gap-x-0.5 grid-cols-[auto,1fr]">
|
<div class="grid grid-rows-none gap-y-1 gap-x-0.5 grid-cols-[auto,1fr]">
|
||||||
<NameValue name="Name:" value=name />
|
<NameValue name="Name:" value=name />
|
||||||
<NameValue name="Method:" value=method.to_string() />
|
<NameValue name="Method:" value=method />
|
||||||
<NameValue name="Set By:" value=set_by />
|
<NameValue name="Set By:" value=set_by />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -21,9 +21,9 @@ pub fn ProblemInfo(problem: models::Problem) -> impl IntoView {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn NameValue(#[prop(into)] name: String, #[prop(into)] value: String) -> impl IntoView {
|
fn NameValue(#[prop(into)] name: Signal<String>, #[prop(into)] value: Signal<String>) -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<p class="text-sm font-light mr-4 text-right text-orange-300">{name}</p>
|
<p class="text-sm font-light mr-4 text-right text-orange-300">{name.get()}</p>
|
||||||
<p class="text-white">{value}</p>
|
<p class="text-white">{value.get()}</p>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
crates/ascend/src/components/show_some.rs
Normal file
30
crates/ascend/src/components/show_some.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn ShowSome<T, C, IV>(#[prop(into)] sig: Signal<Option<T>>, foo: C) -> impl IntoView
|
||||||
|
where
|
||||||
|
T: Clone + Send + Sync + 'static,
|
||||||
|
C: Fn(Signal<T>) -> IV + Sync + Send + 'static,
|
||||||
|
IV: IntoView,
|
||||||
|
{
|
||||||
|
tracing::trace!("Enter");
|
||||||
|
|
||||||
|
view! {
|
||||||
|
// <Show when=move || sig.get().is_some() fallback=|| ()>
|
||||||
|
// {move || {
|
||||||
|
// let new = signal()
|
||||||
|
|
||||||
|
// sig
|
||||||
|
// .with(|opt| {
|
||||||
|
// if let Some(inner) = opt.clone() {
|
||||||
|
// let new_signal = Signal::derive(move || sig.get().unwrap());
|
||||||
|
// .into_any()
|
||||||
|
// } else {
|
||||||
|
// view! {}.into_any()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }}
|
||||||
|
// </Show>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ pub mod components {
|
|||||||
pub mod outlined_box;
|
pub mod outlined_box;
|
||||||
pub mod problem;
|
pub mod problem;
|
||||||
pub mod problem_info;
|
pub mod problem_info;
|
||||||
|
pub mod show_some;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod gradient;
|
pub mod gradient;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ fn Hold(wall_uid: models::WallUid, hold: models::Hold) -> impl IntoView {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let upload = Action::from(ServerAction::<SetImage>::new());
|
let upload = ServerAction::<SetImage>::new();
|
||||||
|
|
||||||
let hold = Signal::derive(move || {
|
let hold = Signal::derive(move || {
|
||||||
let refreshed = upload.value().get().map(Result::unwrap);
|
let refreshed = upload.value().get().map(Result::unwrap);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub fn Settings() -> impl IntoView {
|
|||||||
#[component]
|
#[component]
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn Import(wall_uid: WallUid) -> impl IntoView {
|
fn Import(wall_uid: WallUid) -> impl IntoView {
|
||||||
let import_from_mini_moonboard = Action::from(ServerAction::<ImportFromMiniMoonboard>::new());
|
let import_from_mini_moonboard = ServerAction::<ImportFromMiniMoonboard>::new();
|
||||||
|
|
||||||
let onclick = move |_mouse_event| {
|
let onclick = move |_mouse_event| {
|
||||||
import_from_mini_moonboard.dispatch(ImportFromMiniMoonboard { wall_uid });
|
import_from_mini_moonboard.dispatch(ImportFromMiniMoonboard { wall_uid });
|
||||||
@@ -44,7 +44,7 @@ fn Import(wall_uid: WallUid) -> impl IntoView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(name = ImportFromMiniMoonboard)]
|
#[server]
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
async fn import_from_mini_moonboard(wall_uid: WallUid) -> Result<(), ServerFnError> {
|
async fn import_from_mini_moonboard(wall_uid: WallUid) -> Result<(), ServerFnError> {
|
||||||
use crate::server::config::Config;
|
use crate::server::config::Config;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
// | 14 days ago: <Attempt> |
|
// | 14 days ago: <Attempt> |
|
||||||
// +--------------------------------------+
|
// +--------------------------------------+
|
||||||
|
|
||||||
|
use crate::codec::ron::RonEncoded;
|
||||||
use crate::components::ProblemInfo;
|
use crate::components::ProblemInfo;
|
||||||
use crate::components::attempt::Attempt;
|
use crate::components::attempt::Attempt;
|
||||||
use crate::components::button::Button;
|
use crate::components::button::Button;
|
||||||
@@ -40,6 +41,7 @@ use crate::components::icons::Icon;
|
|||||||
use crate::gradient::Gradient;
|
use crate::gradient::Gradient;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::HoldRole;
|
use crate::models::HoldRole;
|
||||||
|
use crate::server_functions;
|
||||||
use leptos::Params;
|
use leptos::Params;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::params::Params;
|
use leptos_router::params::Params;
|
||||||
@@ -57,8 +59,6 @@ pub fn Wall() -> impl IntoView {
|
|||||||
|
|
||||||
let route_params = leptos_router::hooks::use_params::<RouteParams>();
|
let route_params = leptos_router::hooks::use_params::<RouteParams>();
|
||||||
|
|
||||||
let (problem_uid, set_problem_uid) = leptos_router::hooks::query_signal::<models::ProblemUid>("problem");
|
|
||||||
|
|
||||||
let wall_uid = Signal::derive(move || {
|
let wall_uid = Signal::derive(move || {
|
||||||
route_params
|
route_params
|
||||||
.get()
|
.get()
|
||||||
@@ -68,28 +68,75 @@ pub fn Wall() -> impl IntoView {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let wall = crate::resources::wall_by_uid(wall_uid);
|
let wall = crate::resources::wall_by_uid(wall_uid);
|
||||||
let problem = crate::resources::problem_by_uid(wall_uid, problem_uid.into());
|
|
||||||
|
let header_items = move || HeaderItems {
|
||||||
|
left: vec![],
|
||||||
|
middle: vec![HeaderItem {
|
||||||
|
text: "ASCEND".to_string(),
|
||||||
|
link: None,
|
||||||
|
}],
|
||||||
|
right: vec![
|
||||||
|
HeaderItem {
|
||||||
|
text: "Routes".to_string(),
|
||||||
|
link: Some(format!("/wall/{}/routes", wall_uid.get())),
|
||||||
|
},
|
||||||
|
HeaderItem {
|
||||||
|
text: "Holds".to_string(),
|
||||||
|
link: Some(format!("/wall/{}/edit", wall_uid.get())),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
leptos::view! {
|
||||||
|
<div class="min-h-screen min-w-screen bg-neutral-950">
|
||||||
|
<StyledHeader items=Signal::derive(header_items) />
|
||||||
|
|
||||||
|
<div class="m-2">
|
||||||
|
<Transition fallback=|| ()>
|
||||||
|
{move || Suspend::new(async move {
|
||||||
|
tracing::info!("executing main suspend");
|
||||||
|
let wall = wall.await?;
|
||||||
|
let v = view! { <WithWall wall /> };
|
||||||
|
Ok::<_, ServerFnError>(v)
|
||||||
|
})}
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
fn WithProblem(#[prop(into)] problem: Signal<models::Problem>) -> impl IntoView {
|
||||||
|
tracing::trace!("Enter");
|
||||||
|
|
||||||
|
view! { <ProblemInfo problem /> }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
|
||||||
|
tracing::trace!("Enter");
|
||||||
|
|
||||||
|
let (problem_uid, set_problem_uid) = leptos_router::hooks::query_signal::<models::ProblemUid>("problem");
|
||||||
|
|
||||||
|
let wall_uid = Signal::derive(move || wall.read().uid);
|
||||||
|
|
||||||
|
let problem = crate::resources::problem_by_uid_optional(wall_uid, problem_uid.into());
|
||||||
let user_interaction = crate::resources::user_interaction(wall_uid, problem_uid.into());
|
let user_interaction = crate::resources::user_interaction(wall_uid, problem_uid.into());
|
||||||
|
|
||||||
// merge outer option (resource hasn't resolved yet) with inner option (there is no problem for the wall)
|
let submit_attempt = ServerAction::<RonEncoded<server_functions::UpsertTodaysAttempt>>::new();
|
||||||
let problem_sig2 = Signal::derive(move || problem.get().transpose().map(Option::flatten));
|
|
||||||
|
|
||||||
let fn_next_problem = move |wall: &models::Wall| {
|
let fn_next_problem = move |wall: &models::Wall| {
|
||||||
set_problem_uid.set(wall.random_problem());
|
set_problem_uid.set(wall.random_problem());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set a problem when wall is set (loaded)
|
// Set a problem when wall is set (loaded)
|
||||||
Effect::new(move |_prev_value| match &*wall.read() {
|
Effect::new(move |_prev_value| {
|
||||||
Some(Ok(wall)) => {
|
|
||||||
if problem_uid.get().is_none() {
|
if problem_uid.get().is_none() {
|
||||||
tracing::debug!("Setting next problem");
|
tracing::debug!("Setting next problem");
|
||||||
fn_next_problem(wall);
|
fn_next_problem(&wall.read());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Some(Err(err)) => {
|
|
||||||
tracing::error!("Error getting wall: {err}");
|
|
||||||
}
|
|
||||||
None => {}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let (attempt_today, set_attempt_today) = signal(None);
|
let (attempt_today, set_attempt_today) = signal(None);
|
||||||
@@ -112,24 +159,6 @@ pub fn Wall() -> impl IntoView {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let header_items = move || HeaderItems {
|
|
||||||
left: vec![],
|
|
||||||
middle: vec![HeaderItem {
|
|
||||||
text: "ASCEND".to_string(),
|
|
||||||
link: None,
|
|
||||||
}],
|
|
||||||
right: vec![
|
|
||||||
HeaderItem {
|
|
||||||
text: "Routes".to_string(),
|
|
||||||
link: Some(format!("/wall/{}/routes", wall_uid.get())),
|
|
||||||
},
|
|
||||||
HeaderItem {
|
|
||||||
text: "Holds".to_string(),
|
|
||||||
link: Some(format!("/wall/{}/edit", wall_uid.get())),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// onclick handler helper
|
// onclick handler helper
|
||||||
let set_or_deselect = |s: WriteSignal<Option<models::Attempt>>, v: models::Attempt| {
|
let set_or_deselect = |s: WriteSignal<Option<models::Attempt>>, v: models::Attempt| {
|
||||||
s.update(|s| match s {
|
s.update(|s| match s {
|
||||||
@@ -143,6 +172,11 @@ pub fn Wall() -> impl IntoView {
|
|||||||
};
|
};
|
||||||
let onclick_flash = move |_| {
|
let onclick_flash = move |_| {
|
||||||
set_or_deselect(set_attempt_today, models::Attempt::Flash);
|
set_or_deselect(set_attempt_today, models::Attempt::Flash);
|
||||||
|
submit_attempt.dispatch(RonEncoded(server_functions::UpsertTodaysAttempt {
|
||||||
|
wall_uid: wall_uid.get(),
|
||||||
|
problem_uid: problem.get().uid,
|
||||||
|
attempt: models::Attempt::Flash,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
let onclick_send = move |_| {
|
let onclick_send = move |_| {
|
||||||
set_or_deselect(set_attempt_today, models::Attempt::Send);
|
set_or_deselect(set_attempt_today, models::Attempt::Send);
|
||||||
@@ -196,15 +230,9 @@ pub fn Wall() -> impl IntoView {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
leptos::view! {
|
// merge outer option (resource hasn't resolved yet) with inner option (there is no problem for the wall)
|
||||||
<div class="min-h-screen min-w-screen bg-neutral-950">
|
let problem_signal = Signal::derive(move || problem.get().transpose().map(Option::flatten));
|
||||||
<StyledHeader items=Signal::derive(header_items) />
|
|
||||||
|
|
||||||
<div class="m-2">
|
|
||||||
<Transition fallback=|| ()>
|
|
||||||
{move || Suspend::new(async move {
|
|
||||||
tracing::info!("executing main suspend");
|
|
||||||
let wall = wall.await?;
|
|
||||||
let grid = {
|
let grid = {
|
||||||
let wall = wall.clone();
|
let wall = wall.clone();
|
||||||
view! {
|
view! {
|
||||||
@@ -216,9 +244,7 @@ pub fn Wall() -> impl IntoView {
|
|||||||
Suspend::new(async move {
|
Suspend::new(async move {
|
||||||
let wall = wall.clone();
|
let wall = wall.clone();
|
||||||
tracing::info!("executing grid suspend");
|
tracing::info!("executing grid suspend");
|
||||||
let view = view! {
|
let view = view! { <Grid wall=wall.get() problem=problem_signal /> };
|
||||||
<Grid wall=wall.clone() problem=problem_sig2 />
|
|
||||||
};
|
|
||||||
Ok::<_, ServerFnError>(view)
|
Ok::<_, ServerFnError>(view)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -226,6 +252,7 @@ pub fn Wall() -> impl IntoView {
|
|||||||
</Transition>
|
</Transition>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let v = view! {
|
let v = view! {
|
||||||
<div class="inline-grid grid-cols-1 gap-8 md:grid-cols-[auto,1fr]">
|
<div class="inline-grid grid-cols-1 gap-8 md:grid-cols-[auto,1fr]">
|
||||||
<div>{grid}</div>
|
<div>{grid}</div>
|
||||||
@@ -240,7 +267,7 @@ pub fn Wall() -> impl IntoView {
|
|||||||
<Button
|
<Button
|
||||||
icon=Icon::ArrowPath
|
icon=Icon::ArrowPath
|
||||||
text="Next problem"
|
text="Next problem"
|
||||||
onclick=move |_| fn_next_problem(&wall)
|
onclick=move |_| fn_next_problem(&wall.read())
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -250,7 +277,7 @@ pub fn Wall() -> impl IntoView {
|
|||||||
<Section title="Problem">
|
<Section title="Problem">
|
||||||
<Transition fallback=|| ()>
|
<Transition fallback=|| ()>
|
||||||
{move || Suspend::new(async move {
|
{move || Suspend::new(async move {
|
||||||
tracing::info!("executing probleminfo suspend");
|
tracing::info!("executing problem suspend");
|
||||||
let problem = problem.await?;
|
let problem = problem.await?;
|
||||||
let view = problem
|
let view = problem
|
||||||
.map(|problem| {
|
.map(|problem| {
|
||||||
@@ -278,20 +305,7 @@ pub fn Wall() -> impl IntoView {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
};
|
};
|
||||||
Ok::<_, ServerFnError>(v)
|
|
||||||
})}
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: refactor along these lines to limit suspend nesting in a single component?
|
|
||||||
// #[component]
|
|
||||||
// #[tracing::instrument(skip_all)]
|
|
||||||
// fn WithWall(#[prop(into)] wall: Signal<models::Wall>) -> impl IntoView {
|
|
||||||
// tracing::trace!("Enter");
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
|
|||||||
@@ -16,7 +16,26 @@ pub fn wall_by_uid(wall_uid: Signal<models::WallUid>) -> RonResource<models::Wal
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn problem_by_uid(wall_uid: Signal<models::WallUid>, problem_uid: Signal<Option<models::ProblemUid>>) -> RonResource<Option<models::Problem>> {
|
pub fn problem_by_uid(wall_uid: Signal<models::WallUid>, problem_uid: Signal<models::ProblemUid>) -> RonResource<models::Problem> {
|
||||||
|
Resource::new_with_options(
|
||||||
|
move || (wall_uid.get(), problem_uid.get()),
|
||||||
|
move |(wall_uid, problem_uid)| async move {
|
||||||
|
let Some(problem_uid) = problem_uid else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
crate::server_functions::get_problem_by_uid(wall_uid, problem_uid)
|
||||||
|
.await
|
||||||
|
.map(RonEncoded::into_inner)
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Version of [problem_by_uid] that short circuits if the input problem_uid signal is None.
|
||||||
|
pub fn problem_by_uid_optional(
|
||||||
|
wall_uid: Signal<models::WallUid>,
|
||||||
|
problem_uid: Signal<Option<models::ProblemUid>>,
|
||||||
|
) -> RonResource<Option<models::Problem>> {
|
||||||
Resource::new_with_options(
|
Resource::new_with_options(
|
||||||
move || (wall_uid.get(), problem_uid.get()),
|
move || (wall_uid.get(), problem_uid.get()),
|
||||||
move |(wall_uid, problem_uid)| async move {
|
move |(wall_uid, problem_uid)| async move {
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ use crate::codec::ron::Ron;
|
|||||||
use crate::codec::ron::RonEncoded;
|
use crate::codec::ron::RonEncoded;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::UserInteraction;
|
use crate::models::UserInteraction;
|
||||||
|
use derive_more::Display;
|
||||||
|
use derive_more::Error;
|
||||||
|
use derive_more::From;
|
||||||
|
use leptos::prelude::*;
|
||||||
use leptos::server;
|
use leptos::server;
|
||||||
use server_fn::ServerFnError;
|
use server_fn::ServerFnError;
|
||||||
|
|
||||||
@@ -202,3 +206,74 @@ pub(crate) async fn get_problem_by_uid(
|
|||||||
|
|
||||||
Ok(RonEncoded::new(problem))
|
Ok(RonEncoded::new(problem))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts or updates today's attempt.
|
||||||
|
#[server(
|
||||||
|
input = Ron,
|
||||||
|
output = Ron,
|
||||||
|
custom = RonEncoded
|
||||||
|
)]
|
||||||
|
#[tracing::instrument(err(Debug))]
|
||||||
|
pub(crate) async fn upsert_todays_attempt(
|
||||||
|
wall_uid: models::WallUid,
|
||||||
|
problem_uid: models::ProblemUid,
|
||||||
|
attempt: models::Attempt,
|
||||||
|
) -> Result<RonEncoded<models::UserInteraction>, ServerFnError> {
|
||||||
|
use crate::server::db::Database;
|
||||||
|
use crate::server::db::DatabaseOperationError;
|
||||||
|
use leptos::prelude::expect_context;
|
||||||
|
|
||||||
|
tracing::trace!("Enter");
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Display, From)]
|
||||||
|
enum Error {
|
||||||
|
#[display("Wall not found: {_0:?}")]
|
||||||
|
WallNotFound(#[error(not(source))] models::WallUid),
|
||||||
|
|
||||||
|
DatabaseOperation(DatabaseOperationError),
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner(wall_uid: models::WallUid, problem_uid: models::ProblemUid, attempt: models::Attempt) -> Result<UserInteraction, Error> {
|
||||||
|
let db = expect_context::<Database>();
|
||||||
|
|
||||||
|
let user_interaction = db
|
||||||
|
.write(|txn| {
|
||||||
|
let mut user_table = txn.open_table(crate::server::db::current::TABLE_USER)?;
|
||||||
|
|
||||||
|
let key = (wall_uid, problem_uid);
|
||||||
|
|
||||||
|
// Pop or default
|
||||||
|
let mut user_interaction = user_table
|
||||||
|
.remove(key)?
|
||||||
|
.map(|guard| guard.value())
|
||||||
|
.unwrap_or_else(|| models::UserInteraction::new(wall_uid, problem_uid));
|
||||||
|
|
||||||
|
// If the last entry is from today, remove it
|
||||||
|
if let Some(entry) = user_interaction.attempted_on.last_entry() {
|
||||||
|
let today_local_naive = chrono::Local::now().date_naive();
|
||||||
|
|
||||||
|
let entry_date = entry.key();
|
||||||
|
let entry_date_local_naive = entry_date.with_timezone(&chrono::Local).date_naive();
|
||||||
|
|
||||||
|
if entry_date_local_naive == today_local_naive {
|
||||||
|
entry.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user_interaction.attempted_on.insert(chrono::Utc::now(), attempt);
|
||||||
|
|
||||||
|
user_table.insert(key, user_interaction.clone())?;
|
||||||
|
|
||||||
|
Ok(user_interaction)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(user_interaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner(wall_uid, problem_uid, attempt)
|
||||||
|
.await
|
||||||
|
.map_err(error_reporter::Report::new)
|
||||||
|
.map_err(ServerFnError::new)
|
||||||
|
.map(RonEncoded::new)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user