Merge pull request 'Fix time not implemented on wasm on profile page' (#135) from 134-fix-time-not-implemented-on-wasm into main
Reviewed-on: LibreTunes/LibreTunes#135
This commit is contained in:
commit
b800e4ee47
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -378,13 +378,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.37"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.4",
|
||||
]
|
||||
|
||||
@ -690,11 +693,11 @@ checksum = "03fc05c17098f21b89bc7d98fe1dd3cce2c11c2ad8e145f2a44fe08ed28eb559"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"diesel_derives",
|
||||
"itoa",
|
||||
"pq-sys",
|
||||
"r2d2",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1834,6 +1837,7 @@ dependencies = [
|
||||
"axum",
|
||||
"axum-login",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"console_error_panic_hook",
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
@ -1857,7 +1861,6 @@ dependencies = [
|
||||
"server_fn",
|
||||
"symphonia",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower 0.5.1",
|
||||
"tower-http",
|
||||
|
@ -19,11 +19,10 @@ wasm-bindgen = { version = "=0.2.93", default-features = false, optional = true
|
||||
leptos_icons = { version = "0.3.0" }
|
||||
icondata = { version = "0.3.0" }
|
||||
dotenv = { version = "0.15.0", optional = true }
|
||||
diesel = { version = "2.1.4", features = ["postgres", "r2d2", "time"], default-features = false, optional = true }
|
||||
diesel = { version = "2.1.4", features = ["postgres", "r2d2", "chrono"], default-features = false, optional = true }
|
||||
lazy_static = { version = "1.4.0", optional = true }
|
||||
serde = { version = "1.0.195", features = ["derive"], default-features = false }
|
||||
openssl = { version = "0.10.63", optional = true }
|
||||
time = { version = "0.3.34", features = ["serde"], default-features = false }
|
||||
diesel_migrations = { version = "2.1.0", optional = true }
|
||||
pbkdf2 = { version = "0.12.2", features = ["simple"], optional = true }
|
||||
tokio = { version = "1", optional = true, features = ["rt-multi-thread"] }
|
||||
@ -42,6 +41,7 @@ flexi_logger = { version = "0.28.0", optional = true, default-features = false }
|
||||
web-sys = "0.3.69"
|
||||
leptos-use = "0.13.5"
|
||||
image-convert = { version = "0.18.0", optional = true, default-features = false }
|
||||
chrono = { version = "0.4.38", default-features = false, features = ["serde", "clock"] }
|
||||
|
||||
[features]
|
||||
hydrate = [
|
||||
@ -50,6 +50,7 @@ hydrate = [
|
||||
"leptos_router/hydrate",
|
||||
"console_error_panic_hook",
|
||||
"wasm-bindgen",
|
||||
"chrono/wasmbind",
|
||||
]
|
||||
ssr = [
|
||||
"dep:leptos_axum",
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::models::Artist;
|
||||
use crate::components::dashboard_tile::DashboardTile;
|
||||
|
||||
use time::Date;
|
||||
use chrono::NaiveDate;
|
||||
|
||||
/// Holds information about an album
|
||||
///
|
||||
@ -14,7 +14,7 @@ pub struct AlbumData {
|
||||
/// Album artists
|
||||
pub artists: Vec<Artist>,
|
||||
/// Album release date
|
||||
pub release_date: Option<Date>,
|
||||
pub release_date: Option<NaiveDate>,
|
||||
/// Path to album image, relative to the root of the web server.
|
||||
/// For example, `"/assets/images/Album.jpg"`
|
||||
pub image_path: String,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::time::SystemTime;
|
||||
use chrono::NaiveDateTime;
|
||||
use leptos::*;
|
||||
use crate::models::HistoryEntry;
|
||||
use crate::models::Song;
|
||||
@ -25,7 +25,7 @@ pub async fn get_history(limit: Option<i64>) -> Result<Vec<HistoryEntry>, Server
|
||||
|
||||
/// Get the listen dates and songs of the current user.
|
||||
#[server(endpoint = "history/get_songs")]
|
||||
pub async fn get_history_songs(limit: Option<i64>) -> Result<Vec<(SystemTime, Song)>, ServerFnError> {
|
||||
pub async fn get_history_songs(limit: Option<i64>) -> Result<Vec<(NaiveDateTime, Song)>, ServerFnError> {
|
||||
let user = get_user().await?;
|
||||
let db_con = &mut get_db_conn();
|
||||
let songs = user.get_history_songs(limit, db_con)
|
||||
|
@ -6,7 +6,7 @@ use cfg_if::cfg_if;
|
||||
use crate::songdata::SongData;
|
||||
use crate::artistdata::ArtistData;
|
||||
|
||||
use std::time::SystemTime;
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
@ -67,7 +67,7 @@ pub async fn upload_picture(data: MultipartData) -> Result<(), ServerFnError> {
|
||||
/// Returns a list of tuples with the date the song was listened to
|
||||
/// and the song data, sorted by date (most recent first).
|
||||
#[server(endpoint = "/profile/recent_songs")]
|
||||
pub async fn recent_songs(for_user_id: i32, limit: Option<i64>) -> Result<Vec<(SystemTime, SongData)>, ServerFnError> {
|
||||
pub async fn recent_songs(for_user_id: i32, limit: Option<i64>) -> Result<Vec<(NaiveDateTime, SongData)>, ServerFnError> {
|
||||
let mut db_con = get_db_conn();
|
||||
|
||||
// Get the ids of the most recent songs listened to
|
||||
@ -108,7 +108,7 @@ pub async fn recent_songs(for_user_id: i32, limit: Option<i64>) -> Result<Vec<(S
|
||||
.load(&mut db_con)?;
|
||||
|
||||
// Process the history data into a map of song ids to song data
|
||||
let mut history_songs: HashMap<i32, (SystemTime, SongData)> = HashMap::with_capacity(history.len());
|
||||
let mut history_songs: HashMap<i32, (NaiveDateTime, SongData)> = HashMap::with_capacity(history.len());
|
||||
|
||||
for (history, song, album, artist, like, dislike) in history {
|
||||
let song_id = history.song_id;
|
||||
@ -148,7 +148,7 @@ pub async fn recent_songs(for_user_id: i32, limit: Option<i64>) -> Result<Vec<(S
|
||||
}
|
||||
|
||||
// Sort the songs by date
|
||||
let mut history_songs: Vec<(SystemTime, SongData)> = history_songs.into_values().collect();
|
||||
let mut history_songs: Vec<(NaiveDateTime, SongData)> = history_songs.into_values().collect();
|
||||
history_songs.sort_by(|a, b| b.0.cmp(&a.0));
|
||||
Ok(history_songs)
|
||||
}
|
||||
@ -158,7 +158,7 @@ pub async fn recent_songs(for_user_id: i32, limit: Option<i64>) -> Result<Vec<(S
|
||||
/// If not provided, all songs listened to in the date range are returned.
|
||||
/// Returns a list of tuples with the play count and the song data, sorted by play count (most played first).
|
||||
#[server(endpoint = "/profile/top_songs")]
|
||||
pub async fn top_songs(for_user_id: i32, start_date: SystemTime, end_date: SystemTime, limit: Option<i64>)
|
||||
pub async fn top_songs(for_user_id: i32, start_date: NaiveDateTime, end_date: NaiveDateTime, limit: Option<i64>)
|
||||
-> Result<Vec<(i64, SongData)>, ServerFnError>
|
||||
{
|
||||
let mut db_con = get_db_conn();
|
||||
@ -259,7 +259,7 @@ pub async fn top_songs(for_user_id: i32, start_date: SystemTime, end_date: Syste
|
||||
/// If not provided, all artists listened to in the date range are returned.
|
||||
/// Returns a list of tuples with the play count and the artist data, sorted by play count (most played first).
|
||||
#[server(endpoint = "/profile/top_artists")]
|
||||
pub async fn top_artists(for_user_id: i32, start_date: SystemTime, end_date: SystemTime, limit: Option<i64>)
|
||||
pub async fn top_artists(for_user_id: i32, start_date: NaiveDateTime, end_date: NaiveDateTime, limit: Option<i64>)
|
||||
-> Result<Vec<(i64, ArtistData)>, ServerFnError>
|
||||
{
|
||||
let mut db_con = get_db_conn();
|
||||
|
@ -1,5 +1,4 @@
|
||||
use std::time::SystemTime;
|
||||
use time::Date;
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
@ -39,8 +38,8 @@ pub struct User {
|
||||
#[cfg_attr(feature = "ssr", diesel(deserialize_as = String))]
|
||||
pub password: Option<String>,
|
||||
/// The time the user was created
|
||||
#[cfg_attr(feature = "ssr", diesel(deserialize_as = SystemTime))]
|
||||
pub created_at: Option<SystemTime>,
|
||||
#[cfg_attr(feature = "ssr", diesel(deserialize_as = NaiveDateTime))]
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
/// Whether the user is an admin
|
||||
pub admin: bool,
|
||||
}
|
||||
@ -103,7 +102,7 @@ impl User {
|
||||
///
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn get_history_songs(self: &Self, limit: Option<i64>, conn: &mut PgPooledConn) ->
|
||||
Result<Vec<(SystemTime, Song)>, Box<dyn Error>> {
|
||||
Result<Vec<(NaiveDateTime, Song)>, Box<dyn Error>> {
|
||||
use crate::schema::songs::dsl::*;
|
||||
use crate::schema::song_history::dsl::*;
|
||||
|
||||
@ -467,7 +466,7 @@ pub struct Album {
|
||||
/// The album's title
|
||||
pub title: String,
|
||||
/// The album's release date
|
||||
pub release_date: Option<Date>,
|
||||
pub release_date: Option<NaiveDate>,
|
||||
/// The path to the album's image file
|
||||
pub image_path: Option<String>,
|
||||
}
|
||||
@ -546,7 +545,7 @@ pub struct Song {
|
||||
/// The duration of the song in seconds
|
||||
pub duration: i32,
|
||||
/// The song's release date
|
||||
pub release_date: Option<Date>,
|
||||
pub release_date: Option<NaiveDate>,
|
||||
/// The path to the song's audio file
|
||||
pub storage_path: String,
|
||||
/// The path to the song's image file
|
||||
@ -622,7 +621,7 @@ pub struct HistoryEntry {
|
||||
/// The id of the user who listened to the song
|
||||
pub user_id: i32,
|
||||
/// The date the song was listened to
|
||||
pub date: SystemTime,
|
||||
pub date: NaiveDateTime,
|
||||
/// The id of the song that was listened to
|
||||
pub song_id: i32,
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use leptos::*;
|
||||
use leptos::logging::*;
|
||||
use leptos_router::use_params_map;
|
||||
use leptos_icons::*;
|
||||
use server_fn::error::NoCustomError;
|
||||
@ -17,7 +16,7 @@ use crate::models::User;
|
||||
use crate::users::get_user_by_id;
|
||||
|
||||
/// Duration in seconds backwards from now to aggregate history data for
|
||||
const HISTORY_SECS: u64 = 60 * 60 * 24 * 30;
|
||||
const HISTORY_SECS: i64 = 60 * 60 * 24 * 30;
|
||||
const HISTORY_MESSAGE: &str = "Last Month";
|
||||
|
||||
/// How many top songs to show
|
||||
@ -160,19 +159,7 @@ fn UserProfile(user: User) -> impl IntoView {
|
||||
{user.email}
|
||||
{
|
||||
user.created_at.map(|created_at| {
|
||||
use time::{OffsetDateTime, macros::format_description};
|
||||
let format = format_description!("[month repr:long] [year]");
|
||||
let date_time = Into::<OffsetDateTime>::into(created_at).format(format);
|
||||
|
||||
match date_time {
|
||||
Ok(date_time) => {
|
||||
format!(" • Joined {}", date_time)
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error formatting date: {}", e);
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
format!(" • Joined {}", created_at.format("%B %Y"))
|
||||
})
|
||||
}
|
||||
{
|
||||
@ -191,11 +178,10 @@ fn UserProfile(user: User) -> impl IntoView {
|
||||
#[component]
|
||||
fn TopSongs(#[prop(into)] user_id: MaybeSignal<i32>) -> impl IntoView {
|
||||
let top_songs = create_resource(move || user_id.get(), |user_id| async move {
|
||||
use std::time::{SystemTime, Duration};
|
||||
|
||||
let now = SystemTime::now();
|
||||
let start = now - Duration::from_secs(HISTORY_SECS);
|
||||
let top_songs = top_songs(user_id, start, now, Some(TOP_SONGS_COUNT)).await;
|
||||
use chrono::{Local, Duration};
|
||||
let now = Local::now();
|
||||
let start = now - Duration::seconds(HISTORY_SECS);
|
||||
let top_songs = top_songs(user_id, start.naive_utc(), now.naive_utc(), Some(TOP_SONGS_COUNT)).await;
|
||||
|
||||
top_songs.map(|top_songs| {
|
||||
top_songs.into_iter().map(|(plays, song)| {
|
||||
@ -283,11 +269,11 @@ fn RecentSongs(#[prop(into)] user_id: MaybeSignal<i32>) -> impl IntoView {
|
||||
#[component]
|
||||
fn TopArtists(#[prop(into)] user_id: MaybeSignal<i32>) -> impl IntoView {
|
||||
let top_artists = create_resource(move || user_id.get(), |user_id| async move {
|
||||
use std::time::{SystemTime, Duration};
|
||||
use chrono::{Local, Duration};
|
||||
|
||||
let now = SystemTime::now();
|
||||
let start = now - Duration::from_secs(HISTORY_SECS);
|
||||
let top_artists = top_artists(user_id, start, now, Some(TOP_ARTISTS_COUNT)).await;
|
||||
let now = Local::now();
|
||||
let start = now - Duration::seconds(HISTORY_SECS);
|
||||
let top_artists = top_artists(user_id, start.naive_utc(), now.naive_utc(), Some(TOP_ARTISTS_COUNT)).await;
|
||||
|
||||
top_artists.map(|top_artists| {
|
||||
top_artists.into_iter().map(|(_plays, artist)| {
|
||||
|
@ -2,7 +2,7 @@ use crate::models::{Album, Artist, Song};
|
||||
use crate::components::dashboard_tile::DashboardTile;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use time::Date;
|
||||
use chrono::NaiveDate;
|
||||
|
||||
/// Holds information about a song
|
||||
///
|
||||
@ -22,7 +22,7 @@ pub struct SongData {
|
||||
/// The duration of the song in seconds
|
||||
pub duration: i32,
|
||||
/// The song's release date
|
||||
pub release_date: Option<Date>,
|
||||
pub release_date: Option<NaiveDate>,
|
||||
/// Path to song file, relative to the root of the web server.
|
||||
/// For example, `"/assets/audio/Song.mp3"`
|
||||
pub song_path: String,
|
||||
|
@ -10,7 +10,7 @@ cfg_if! {
|
||||
use diesel::prelude::*;
|
||||
use log::*;
|
||||
use server_fn::error::NoCustomError;
|
||||
use time::Date;
|
||||
use chrono::NaiveDate;
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,15 +124,14 @@ async fn validate_track_number(track_number: Field<'static>) -> Result<Option<i3
|
||||
/// Validate the release date in a multipart field
|
||||
/// Expects a field with a release date, and ensures it is a valid date in the format [year]-[month]-[day]
|
||||
#[cfg(feature = "ssr")]
|
||||
async fn validate_release_date(release_date: Field<'static>) -> Result<Option<Date>, ServerFnError> {
|
||||
async fn validate_release_date(release_date: Field<'static>) -> Result<Option<NaiveDate>, ServerFnError> {
|
||||
match release_date.text().await {
|
||||
Ok(release_date) => {
|
||||
if release_date.trim().is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let date_format = time::macros::format_description!("[year]-[month]-[day]");
|
||||
let release_date = Date::parse(&release_date.trim(), date_format);
|
||||
let release_date = NaiveDate::parse_from_str(&release_date.trim(), "%Y-%m-%d");
|
||||
|
||||
match release_date {
|
||||
Ok(release_date) => Ok(Some(release_date)),
|
||||
@ -181,8 +180,7 @@ pub async fn upload(data: MultipartData) -> Result<(), ServerFnError> {
|
||||
ServerError("Title field required and must precede file field".to_string()))?;
|
||||
|
||||
let clean_title = title.replace(" ", "_").replace("/", "_");
|
||||
let date_format = time::macros::format_description!("[year]-[month]-[day]_[hour]:[minute]:[second]");
|
||||
let date_str = time::OffsetDateTime::now_utc().format(date_format).unwrap_or_default();
|
||||
let date_str = chrono::Utc::now().format("%Y-%m-%d_%H:%M:%S").to_string();
|
||||
let upload_path = format!("assets/audio/upload-{}_{}.mp3", date_str, clean_title);
|
||||
file_name = Some(format!("upload-{}_{}.mp3", date_str, clean_title));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user