diff --git a/assets/i18n/en-US/main.ftl b/assets/i18n/en-US/main.ftl index d0dd8c2..176a1d1 100644 --- a/assets/i18n/en-US/main.ftl +++ b/assets/i18n/en-US/main.ftl @@ -32,7 +32,7 @@ home-sub = news and updates. home-all-posts = All posts home-recent = Recent posts home-tagline = guitar player - original songs, albums, and notes -home-sections = about/ blog/ audio/ songs/ +home-picks = have a listen home-no-posts = no published posts yet blog-title = Blog blog-sub = published article(s) diff --git a/assets/i18n/en/main.ftl b/assets/i18n/en/main.ftl index d0dd8c2..176a1d1 100644 --- a/assets/i18n/en/main.ftl +++ b/assets/i18n/en/main.ftl @@ -32,7 +32,7 @@ home-sub = news and updates. home-all-posts = All posts home-recent = Recent posts home-tagline = guitar player - original songs, albums, and notes -home-sections = about/ blog/ audio/ songs/ +home-picks = have a listen home-no-posts = no published posts yet blog-title = Blog blog-sub = published article(s) diff --git a/assets/i18n/sk/main.ftl b/assets/i18n/sk/main.ftl index 6f39a90..736ef5d 100644 --- a/assets/i18n/sk/main.ftl +++ b/assets/i18n/sk/main.ftl @@ -32,7 +32,7 @@ home-sub = novinky a aktuality. home-all-posts = Všetky príspevky home-recent = Posledné príspevky home-tagline = gitarista - autorské skladby, albumy a poznámky -home-sections = about/ blog/ audio/ songs/ +home-picks = vypočuj si home-no-posts = zatiaľ žiadne zverejnené príspevky blog-title = Blog blog-sub = zverejnené články diff --git a/assets/views/home/index.html b/assets/views/home/index.html index f036e7b..ef387d7 100644 --- a/assets/views/home/index.html +++ b/assets/views/home/index.html @@ -17,9 +17,57 @@

→ {{ t(key="home-tagline", lang=lang | default(value='sk')) }}

-

{{ t(key="home-sections", lang=lang | default(value='sk')) }}

+{% if featured_track or featured_album %} +
+

# {{ t(key="home-picks", lang=lang | default(value='sk')) }}

+
+ {% if featured_track %} +
+
+ ~/audio/tracks/{{ featured_track.slug }} + {{ t(key="song", lang=lang | default(value='sk')) }} +
+
+

{{ featured_track.title }}

+
+ + {{ featured_track.title }} +
+
+
+ {% endif %} + {% if featured_album %} +
+
+ ~/audio/{{ featured_album.slug }}/ + {{ t(key="album", lang=lang | default(value='sk')) }} +
+
+ {% if featured_album.cover_image_id %} + + {% endif %} +

{{ featured_album.title }}

+ {% if featured_album.artist %} +

{{ featured_album.artist }}

+ {% endif %} + {% if featured_album.description %} +

{{ featured_album.description }}

+ {% endif %} +
+ + {{ t(key="audio-open", lang=lang | default(value='sk')) }} +
+
+
+ {% endif %} +
+
+{% endif %} +

# {{ t(key="home-recent", lang=lang | default(value='sk')) }} ({{ articles | length }})

{% if articles | length > 0 %} @@ -63,9 +111,57 @@

{{ t(key="home-tagline", lang=lang | default(value='sk')) }}

-

{{ t(key="home-sections", lang=lang | default(value='sk')) }}

+{% if featured_track or featured_album %} +
+

# {{ t(key="home-picks", lang=lang | default(value='sk')) }}

+
+ {% if featured_track %} +
+
+ ~/audio/tracks/{{ featured_track.slug }} + {{ t(key="song", lang=lang | default(value='sk')) }} +
+
+

{{ featured_track.title }}

+
+ + {{ featured_track.title }} +
+
+
+ {% endif %} + {% if featured_album %} +
+
+ ~/audio/{{ featured_album.slug }}/ + {{ t(key="album", lang=lang | default(value='sk')) }} +
+
+ {% if featured_album.cover_image_id %} + + {% endif %} +

{{ featured_album.title }}

+ {% if featured_album.artist %} +

{{ featured_album.artist }}

+ {% endif %} + {% if featured_album.description %} +

{{ featured_album.description }}

+ {% endif %} +
+ + {{ t(key="audio-open", lang=lang | default(value='sk')) }} +
+
+
+ {% endif %} +
+
+{% endif %} +

# {{ t(key="home-recent", lang=lang | default(value='sk')) }} ({{ articles | length }})

{% if articles | length > 0 %} diff --git a/migration/src/m20260517_000003_blog_articles.rs b/migration/src/m20260517_000003_blog_articles.rs index 3cb95b6..de86fda 100644 --- a/migration/src/m20260517_000003_blog_articles.rs +++ b/migration/src/m20260517_000003_blog_articles.rs @@ -33,8 +33,17 @@ impl MigrationTrait for Migration { Table::create() .table(BlogArticles::Table) .if_not_exists() - .col(ColumnDef::new(BlogArticles::Id).uuid().not_null().primary_key()) - .col(ColumnDef::new(BlogArticles::Title).string_len(500).not_null()) + .col( + ColumnDef::new(BlogArticles::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(BlogArticles::Title) + .string_len(500) + .not_null(), + ) .col( ColumnDef::new(BlogArticles::Slug) .string_len(500) @@ -42,7 +51,11 @@ impl MigrationTrait for Migration { .unique_key(), ) .col(ColumnDef::new(BlogArticles::Content).text().not_null()) - .col(ColumnDef::new(BlogArticles::Excerpt).string_len(1000).null()) + .col( + ColumnDef::new(BlogArticles::Excerpt) + .string_len(1000) + .null(), + ) .col( ColumnDef::new(BlogArticles::Published) .boolean() diff --git a/migration/src/m20260517_000004_audit_logs.rs b/migration/src/m20260517_000004_audit_logs.rs index 5241a7b..f7dcd66 100644 --- a/migration/src/m20260517_000004_audit_logs.rs +++ b/migration/src/m20260517_000004_audit_logs.rs @@ -30,7 +30,12 @@ impl MigrationTrait for Migration { Table::create() .table(AuditLogs::Table) .if_not_exists() - .col(ColumnDef::new(AuditLogs::Id).uuid().not_null().primary_key()) + .col( + ColumnDef::new(AuditLogs::Id) + .uuid() + .not_null() + .primary_key(), + ) .col(ColumnDef::new(AuditLogs::AdminUserId).integer().not_null()) .col(ColumnDef::new(AuditLogs::Action).string_len(100).not_null()) .col(ColumnDef::new(AuditLogs::TargetType).string_len(50).null()) diff --git a/migration/src/m20260517_000005_audio_albums.rs b/migration/src/m20260517_000005_audio_albums.rs index 2564b96..74ca7bd 100644 --- a/migration/src/m20260517_000005_audio_albums.rs +++ b/migration/src/m20260517_000005_audio_albums.rs @@ -34,8 +34,17 @@ impl MigrationTrait for Migration { Table::create() .table(AudioAlbums::Table) .if_not_exists() - .col(ColumnDef::new(AudioAlbums::Id).uuid().not_null().primary_key()) - .col(ColumnDef::new(AudioAlbums::Title).string_len(500).not_null()) + .col( + ColumnDef::new(AudioAlbums::Id) + .uuid() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(AudioAlbums::Title) + .string_len(500) + .not_null(), + ) .col( ColumnDef::new(AudioAlbums::Slug) .string_len(500) diff --git a/migration/src/m20260517_000006_audio_tracks.rs b/migration/src/m20260517_000006_audio_tracks.rs index e69a6a0..47ecae8 100644 --- a/migration/src/m20260517_000006_audio_tracks.rs +++ b/migration/src/m20260517_000006_audio_tracks.rs @@ -32,9 +32,18 @@ impl MigrationTrait for Migration { Table::create() .table(AudioTracks::Table) .if_not_exists() - .col(ColumnDef::new(AudioTracks::Id).uuid().not_null().primary_key()) + .col( + ColumnDef::new(AudioTracks::Id) + .uuid() + .not_null() + .primary_key(), + ) .col(ColumnDef::new(AudioTracks::AlbumId).uuid().not_null()) - .col(ColumnDef::new(AudioTracks::Title).string_len(500).not_null()) + .col( + ColumnDef::new(AudioTracks::Title) + .string_len(500) + .not_null(), + ) .col(ColumnDef::new(AudioTracks::Slug).string_len(500).not_null()) .col( ColumnDef::new(AudioTracks::AudioFileId) diff --git a/migration/src/m20260517_000007_audio_tags.rs b/migration/src/m20260517_000007_audio_tags.rs index b8e62a9..0a80549 100644 --- a/migration/src/m20260517_000007_audio_tags.rs +++ b/migration/src/m20260517_000007_audio_tags.rs @@ -19,7 +19,12 @@ impl MigrationTrait for Migration { Table::create() .table(AudioTags::Table) .if_not_exists() - .col(ColumnDef::new(AudioTags::Id).uuid().not_null().primary_key()) + .col( + ColumnDef::new(AudioTags::Id) + .uuid() + .not_null() + .primary_key(), + ) .col( ColumnDef::new(AudioTags::Name) .string_len(100) diff --git a/migration/src/m20260517_000011_site_pages.rs b/migration/src/m20260517_000011_site_pages.rs index f0eb15b..8da4a1b 100644 --- a/migration/src/m20260517_000011_site_pages.rs +++ b/migration/src/m20260517_000011_site_pages.rs @@ -21,7 +21,12 @@ impl MigrationTrait for Migration { Table::create() .table(SitePages::Table) .if_not_exists() - .col(ColumnDef::new(SitePages::Id).uuid().not_null().primary_key()) + .col( + ColumnDef::new(SitePages::Id) + .uuid() + .not_null() + .primary_key(), + ) .col( ColumnDef::new(SitePages::Slug) .string_len(100) diff --git a/src/controllers/blog.rs b/src/controllers/blog.rs index 4be4115..646d293 100644 --- a/src/controllers/blog.rs +++ b/src/controllers/blog.rs @@ -1,7 +1,4 @@ -use crate::{ - controllers::admin, - models::_entities::blog_articles, -}; +use crate::{controllers::admin, models::_entities::blog_articles}; use chrono::Utc; use loco_rs::prelude::*; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set}; @@ -195,7 +192,11 @@ async fn admin_update( } #[debug_handler] -async fn admin_delete(auth: auth::JWT, Path(id): Path, State(ctx): State) -> Result { +async fn admin_delete( + auth: auth::JWT, + Path(id): Path, + State(ctx): State, +) -> Result { admin::current_admin(auth, &ctx).await?; let article = find_article_by_id(&ctx, id).await?; article.delete(&ctx.db).await?; @@ -203,7 +204,11 @@ async fn admin_delete(auth: auth::JWT, Path(id): Path, State(ctx): State, State(ctx): State) -> Result { +async fn admin_publish( + auth: auth::JWT, + Path(id): Path, + State(ctx): State, +) -> Result { admin::current_admin(auth, &ctx).await?; let mut article = find_article_by_id(&ctx, id).await?.into_active_model(); article.published = Set(true); @@ -213,7 +218,11 @@ async fn admin_publish(auth: auth::JWT, Path(id): Path, State(ctx): State< } #[debug_handler] -async fn admin_unpublish(auth: auth::JWT, Path(id): Path, State(ctx): State) -> Result { +async fn admin_unpublish( + auth: auth::JWT, + Path(id): Path, + State(ctx): State, +) -> Result { admin::current_admin(auth, &ctx).await?; let mut article = find_article_by_id(&ctx, id).await?.into_active_model(); article.published = Set(false); diff --git a/src/controllers/frontend.rs b/src/controllers/frontend.rs index 7699444..a6d2b6c 100644 --- a/src/controllers/frontend.rs +++ b/src/controllers/frontend.rs @@ -1,7 +1,7 @@ use crate::{ controllers::{admin, auth as auth_controller, i18n::current_lang}, models::{ - _entities::{blog_articles, site_pages}, + _entities::{audio_albums, audio_tracks, blog_articles, site_pages}, users::{self, LoginParams}, }, }; @@ -9,7 +9,8 @@ use axum_extra::extract::cookie::CookieJar; use chrono::Utc; use loco_rs::prelude::*; use sea_orm::{ - ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set, + sea_query::Expr, ActiveModelTrait, ColumnTrait, EntityTrait, Order, QueryFilter, QueryOrder, + QuerySelect, Set, }; use serde::Deserialize; use serde_json::json; @@ -59,7 +60,9 @@ fn published_at_for(published: bool) -> Option) -> bool { - value.as_deref().is_some_and(|value| value == "on" || value == "true") + value + .as_deref() + .is_some_and(|value| value == "on" || value == "true") } fn normalize_empty(value: Option) -> Option { @@ -119,11 +122,31 @@ async fn home( .all(&ctx.db) .await?; + // A random published song to suggest on the landing page. + let featured_track = audio_tracks::Entity::find() + .filter(audio_tracks::Column::Published.eq(true)) + .order_by(Expr::cust("RANDOM()"), Order::Asc) + .one(&ctx.db) + .await?; + + // A random published album, never the one the suggested song belongs to. + let mut album_query = + audio_albums::Entity::find().filter(audio_albums::Column::Published.eq(true)); + if let Some(album_id) = featured_track.as_ref().and_then(|track| track.album_id) { + album_query = album_query.filter(audio_albums::Column::Id.ne(album_id)); + } + let featured_album = album_query + .order_by(Expr::cust("RANDOM()"), Order::Asc) + .one(&ctx.db) + .await?; + format::view( &v, "home/index.html", json!({ "articles": articles, + "featured_track": featured_track, + "featured_album": featured_album, "logged_in_admin": logged_in_admin(&ctx, &jar).await, "lang": current_lang(&jar), }), @@ -340,7 +363,11 @@ async fn admin_article_new( State(ctx): State, ) -> Result { admin::current_admin(auth, &ctx).await?; - format::view(&v, "admin/blog/new.html", json!({ "lang": current_lang(&jar) })) + format::view( + &v, + "admin/blog/new.html", + json!({ "lang": current_lang(&jar) }), + ) } #[debug_handler] @@ -445,5 +472,8 @@ pub fn routes() -> Routes { .add("/admin/blog/articles", post(admin_article_create)) .add("/admin/blog/articles/{id}/edit", get(admin_article_edit)) .add("/admin/blog/articles/{id}", post(admin_article_update)) - .add("/admin/blog/articles/{id}/delete", post(admin_article_delete)) + .add( + "/admin/blog/articles/{id}/delete", + post(admin_article_delete), + ) } diff --git a/src/controllers/i18n.rs b/src/controllers/i18n.rs index 72e3341..e6bd529 100644 --- a/src/controllers/i18n.rs +++ b/src/controllers/i18n.rs @@ -13,7 +13,10 @@ pub struct LangForm { } pub fn current_lang(jar: &axum_extra::extract::cookie::CookieJar) -> String { - match jar.get(LANG_COOKIE).map(|cookie| cookie.value().to_string()) { + match jar + .get(LANG_COOKIE) + .map(|cookie| cookie.value().to_string()) + { Some(ref lang) if lang == "en" => "en".to_string(), _ => "sk".to_string(), } diff --git a/src/controllers/media.rs b/src/controllers/media.rs index be7997d..b8b4535 100644 --- a/src/controllers/media.rs +++ b/src/controllers/media.rs @@ -215,7 +215,9 @@ async fn read_multipart_file(mut multipart: Multipart, max_bytes: usize) -> Resu return Ok(data); } - Err(Error::BadRequest("multipart field `file` is required".to_string())) + Err(Error::BadRequest( + "multipart field `file` is required".to_string(), + )) } async fn read_track_upload( @@ -254,7 +256,11 @@ async fn read_track_upload( match name.as_str() { "title" => title = normalize_empty(Some(value)), "track_number" => { - track_number = value.trim().parse::().ok().filter(|number| *number > 0) + track_number = value + .trim() + .parse::() + .ok() + .filter(|number| *number > 0) } "featured" => featured = value == "on" || value == "true" || value == "1", "published" => published = value == "on" || value == "true" || value == "1", @@ -263,7 +269,8 @@ async fn read_track_upload( } } - let data = data.ok_or_else(|| Error::BadRequest("multipart field `file` is required".to_string()))?; + let data = + data.ok_or_else(|| Error::BadRequest("multipart field `file` is required".to_string()))?; if data.is_empty() { return Err(Error::BadRequest("empty file upload".to_string())); } @@ -356,7 +363,11 @@ async fn unique_album_slug(ctx: &AppContext, title: &str) -> Result { Ok(slug) } -async fn unique_track_slug(ctx: &AppContext, album_id: Option, title: &str) -> Result { +async fn unique_track_slug( + ctx: &AppContext, + album_id: Option, + title: &str, +) -> Result { let base = slugify(title); let mut slug = base.clone(); let mut suffix = 2; @@ -411,7 +422,12 @@ async fn track_by_id(ctx: &AppContext, id: Uuid) -> Result .ok_or_else(|| Error::NotFound) } -async fn store_upload(ctx: &AppContext, folder: &str, extension: &str, data: Vec) -> Result { +async fn store_upload( + ctx: &AppContext, + folder: &str, + extension: &str, + data: Vec, +) -> Result { let filename = format!("{}.{}", Uuid::new_v4(), extension); let key = format!("{folder}/{filename}"); ctx.storage @@ -421,7 +437,11 @@ async fn store_upload(ctx: &AppContext, folder: &str, extension: &str, data: Vec } #[debug_handler] -async fn image_upload(auth: auth::JWT, State(ctx): State, multipart: Multipart) -> Result { +async fn image_upload( + auth: auth::JWT, + State(ctx): State, + multipart: Multipart, +) -> Result { admin::current_admin(auth, &ctx).await?; let data = read_multipart_file(multipart, IMAGE_MAX_BYTES).await?; let extension = detect_image_extension(&data)?; @@ -436,7 +456,10 @@ async fn image_upload(auth: auth::JWT, State(ctx): State, multipart: } #[debug_handler] -async fn image_serve(Path(filename): Path, State(ctx): State) -> Result { +async fn image_serve( + Path(filename): Path, + State(ctx): State, +) -> Result { let filename = safe_filename(&filename)?; let extension = filename.rsplit('.').next().unwrap_or(""); let key = format!("{IMAGE_STORAGE_DIR}/{filename}"); @@ -449,7 +472,11 @@ async fn image_serve(Path(filename): Path, State(ctx): State } #[debug_handler] -async fn audio_upload(auth: auth::JWT, State(ctx): State, multipart: Multipart) -> Result { +async fn audio_upload( + auth: auth::JWT, + State(ctx): State, + multipart: Multipart, +) -> Result { admin::current_admin(auth, &ctx).await?; let data = read_multipart_file(multipart, AUDIO_MAX_BYTES).await?; let extension = detect_audio_extension(&data)?; @@ -758,7 +785,9 @@ async fn admin_album_add_track( let track = track_by_id(&ctx, params.track_id).await?; if track.album_id.is_some() { - return Err(Error::BadRequest("song already belongs to an album".to_string())); + return Err(Error::BadRequest( + "song already belongs to an album".to_string(), + )); } let mut active = track.into_active_model(); @@ -828,7 +857,11 @@ async fn create_uploaded_track( let (data, title, track_number, featured, published) = read_track_upload(multipart).await?; let extension = detect_audio_extension(&data)?; let filename = store_upload(ctx, AUDIO_STORAGE_DIR, extension, data).await?; - let title = title.unwrap_or_else(|| filename.trim_end_matches(&format!(".{extension}")).to_string()); + let title = title.unwrap_or_else(|| { + filename + .trim_end_matches(&format!(".{extension}")) + .to_string() + }); audio_tracks::ActiveModel { id: Set(Uuid::new_v4()), @@ -886,7 +919,10 @@ async fn admin_track_delete( let album_id = track.album_id; let _ = ctx .storage - .delete(StdPath::new(&format!("{AUDIO_STORAGE_DIR}/{}", track.audio_file_id))) + .delete(StdPath::new(&format!( + "{AUDIO_STORAGE_DIR}/{}", + track.audio_file_id + ))) .await; track.delete(&ctx.db).await?; if let Some(album_id) = album_id { @@ -938,10 +974,16 @@ async fn admin_track_unpublish( } } -async fn stream_audio_file(config: &Config, filename: &str, headers: &HeaderMap) -> Result { +async fn stream_audio_file( + config: &Config, + filename: &str, + headers: &HeaderMap, +) -> Result { let filename = safe_filename(filename)?; let path = uploads_root(config)?.join(AUDIO_STORAGE_DIR).join(filename); - let mut file = tokio::fs::File::open(&path).await.map_err(|_| Error::NotFound)?; + let mut file = tokio::fs::File::open(&path) + .await + .map_err(|_| Error::NotFound)?; let total_len = file.metadata().await?.len(); let extension = filename.rsplit('.').next().unwrap_or("mp3"); let content_type = audio_content_type(extension); @@ -989,7 +1031,8 @@ fn parse_range(headers: &HeaderMap, total_len: u64) -> Result<(StatusCode, u64, let suffix_range = start.is_empty(); let start = if suffix_range { - let suffix = u64::from_str(end).map_err(|_| Error::BadRequest("invalid range header".to_string()))?; + let suffix = u64::from_str(end) + .map_err(|_| Error::BadRequest("invalid range header".to_string()))?; total_len.saturating_sub(suffix) } else { u64::from_str(start).map_err(|_| Error::BadRequest("invalid range header".to_string()))? @@ -1039,9 +1082,15 @@ async fn track_stream( pub fn routes() -> Routes { Routes::new() - .add("/images/upload", post(image_upload).layer(DefaultBodyLimit::max(IMAGE_MAX_BYTES + 1024 * 1024))) + .add( + "/images/upload", + post(image_upload).layer(DefaultBodyLimit::max(IMAGE_MAX_BYTES + 1024 * 1024)), + ) .add("/images/{filename}", get(image_serve)) - .add("/audio/upload", post(audio_upload).layer(DefaultBodyLimit::max(AUDIO_MAX_BYTES + 1024 * 1024))) + .add( + "/audio/upload", + post(audio_upload).layer(DefaultBodyLimit::max(AUDIO_MAX_BYTES + 1024 * 1024)), + ) .add("/audio/stream/{filename}", get(raw_audio_stream)) .add("/audio/albums", get(public_albums)) .add("/audio/albums/{slug}", get(public_album)) @@ -1050,16 +1099,43 @@ pub fn routes() -> Routes { .add("/audio/tracks/{id}/stream", get(track_stream)) .add("/admin/audio/albums", get(admin_albums)) .add("/admin/audio/albums/create", get(admin_album_new)) - .add("/admin/audio/albums/create", post(admin_album_create).layer(DefaultBodyLimit::max(IMAGE_MAX_BYTES + 1024 * 1024))) + .add( + "/admin/audio/albums/create", + post(admin_album_create).layer(DefaultBodyLimit::max(IMAGE_MAX_BYTES + 1024 * 1024)), + ) .add("/admin/audio/tracks", get(admin_tracks)) .add("/admin/audio/tracks/upload", get(admin_song_upload_form)) - .add("/admin/audio/tracks/upload-file", post(admin_song_upload).layer(DefaultBodyLimit::max(AUDIO_MAX_BYTES + 1024 * 1024))) - .add("/admin/audio/albums/{album_id}/tracks", get(admin_album_tracks)) - .add("/admin/audio/albums/{album_id}/tracks/add", post(admin_album_add_track)) - .add("/admin/audio/albums/{album_id}/tracks/upload", get(admin_track_upload_form)) - .add("/admin/audio/albums/{album_id}/tracks/upload-file", post(admin_track_upload).layer(DefaultBodyLimit::max(AUDIO_MAX_BYTES + 1024 * 1024))) - .add("/admin/audio/tracks/{id}/publish", post(admin_track_publish)) - .add("/admin/audio/tracks/{id}/unpublish", post(admin_track_unpublish)) - .add("/admin/audio/tracks/{id}/remove-from-album", post(admin_track_remove_from_album)) + .add( + "/admin/audio/tracks/upload-file", + post(admin_song_upload).layer(DefaultBodyLimit::max(AUDIO_MAX_BYTES + 1024 * 1024)), + ) + .add( + "/admin/audio/albums/{album_id}/tracks", + get(admin_album_tracks), + ) + .add( + "/admin/audio/albums/{album_id}/tracks/add", + post(admin_album_add_track), + ) + .add( + "/admin/audio/albums/{album_id}/tracks/upload", + get(admin_track_upload_form), + ) + .add( + "/admin/audio/albums/{album_id}/tracks/upload-file", + post(admin_track_upload).layer(DefaultBodyLimit::max(AUDIO_MAX_BYTES + 1024 * 1024)), + ) + .add( + "/admin/audio/tracks/{id}/publish", + post(admin_track_publish), + ) + .add( + "/admin/audio/tracks/{id}/unpublish", + post(admin_track_unpublish), + ) + .add( + "/admin/audio/tracks/{id}/remove-from-album", + post(admin_track_remove_from_album), + ) .add("/admin/audio/tracks/{id}/delete", post(admin_track_delete)) } diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs index 50619df..452b78d 100644 --- a/src/controllers/mod.rs +++ b/src/controllers/mod.rs @@ -1,7 +1,7 @@ pub mod admin; pub mod auth; pub mod blog; -pub mod i18n; pub mod frontend; +pub mod i18n; pub mod media; pub mod pages; diff --git a/src/controllers/pages.rs b/src/controllers/pages.rs index a2133fc..998564c 100644 --- a/src/controllers/pages.rs +++ b/src/controllers/pages.rs @@ -1,7 +1,4 @@ -use crate::{ - controllers::admin, - models::_entities::site_pages, -}; +use crate::{controllers::admin, models::_entities::site_pages}; use loco_rs::prelude::*; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use serde::{Deserialize, Serialize}; diff --git a/src/models/audio_albums.rs b/src/models/audio_albums.rs index 60f5c66..d84e730 100644 --- a/src/models/audio_albums.rs +++ b/src/models/audio_albums.rs @@ -1,5 +1,5 @@ +pub use super::_entities::audio_albums::{ActiveModel, Entity, Model}; use sea_orm::entity::prelude::*; -pub use super::_entities::audio_albums::{ActiveModel, Model, Entity}; pub type AudioAlbums = Entity; #[async_trait::async_trait] diff --git a/src/models/audio_tags.rs b/src/models/audio_tags.rs index d7d923e..f24284c 100644 --- a/src/models/audio_tags.rs +++ b/src/models/audio_tags.rs @@ -1,5 +1,5 @@ +pub use super::_entities::audio_tags::{ActiveModel, Entity, Model}; use sea_orm::entity::prelude::*; -pub use super::_entities::audio_tags::{ActiveModel, Model, Entity}; pub type AudioTags = Entity; #[async_trait::async_trait] diff --git a/src/models/audio_track_tags.rs b/src/models/audio_track_tags.rs index 002e187..e3a2520 100644 --- a/src/models/audio_track_tags.rs +++ b/src/models/audio_track_tags.rs @@ -1,5 +1,5 @@ +pub use super::_entities::audio_track_tags::{ActiveModel, Entity, Model}; use sea_orm::entity::prelude::*; -pub use super::_entities::audio_track_tags::{ActiveModel, Model, Entity}; pub type AudioTrackTags = Entity; #[async_trait::async_trait] diff --git a/src/models/audio_tracks.rs b/src/models/audio_tracks.rs index 0edd09e..bf25553 100644 --- a/src/models/audio_tracks.rs +++ b/src/models/audio_tracks.rs @@ -1,5 +1,5 @@ +pub use super::_entities::audio_tracks::{ActiveModel, Entity, Model}; use sea_orm::entity::prelude::*; -pub use super::_entities::audio_tracks::{ActiveModel, Model, Entity}; pub type AudioTracks = Entity; #[async_trait::async_trait] diff --git a/src/models/audit_logs.rs b/src/models/audit_logs.rs index 2db0fae..787b25d 100644 --- a/src/models/audit_logs.rs +++ b/src/models/audit_logs.rs @@ -1,5 +1,5 @@ +pub use super::_entities::audit_logs::{ActiveModel, Entity, Model}; use sea_orm::entity::prelude::*; -pub use super::_entities::audit_logs::{ActiveModel, Model, Entity}; pub type AuditLogs = Entity; #[async_trait::async_trait] diff --git a/src/models/blog_articles.rs b/src/models/blog_articles.rs index e523f5b..cb2187f 100644 --- a/src/models/blog_articles.rs +++ b/src/models/blog_articles.rs @@ -1,5 +1,5 @@ +pub use super::_entities::blog_articles::{ActiveModel, Entity, Model}; use sea_orm::entity::prelude::*; -pub use super::_entities::blog_articles::{ActiveModel, Model, Entity}; pub type BlogArticles = Entity; #[async_trait::async_trait] diff --git a/src/models/mod.rs b/src/models/mod.rs index 79f8cfa..54d600a 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,9 +1,9 @@ pub mod _entities; -pub mod users; +pub mod audio_albums; pub mod audio_tags; -pub mod audio_tracks; pub mod audio_track_tags; +pub mod audio_tracks; pub mod audit_logs; pub mod blog_articles; -pub mod audio_albums; pub mod site_pages; +pub mod users; diff --git a/src/models/site_pages.rs b/src/models/site_pages.rs index 74ac47e..3216597 100644 --- a/src/models/site_pages.rs +++ b/src/models/site_pages.rs @@ -1,5 +1,5 @@ -use sea_orm::entity::prelude::*; pub use super::_entities::site_pages::{ActiveModel, Entity, Model}; +use sea_orm::entity::prelude::*; pub type SitePages = Entity; #[async_trait::async_trait]