home page improved
This commit is contained in:
@@ -32,7 +32,7 @@ home-sub = news and updates.
|
|||||||
home-all-posts = All posts
|
home-all-posts = All posts
|
||||||
home-recent = Recent posts
|
home-recent = Recent posts
|
||||||
home-tagline = guitar player - original songs, albums, and notes
|
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
|
home-no-posts = no published posts yet
|
||||||
blog-title = Blog
|
blog-title = Blog
|
||||||
blog-sub = published article(s)
|
blog-sub = published article(s)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ home-sub = news and updates.
|
|||||||
home-all-posts = All posts
|
home-all-posts = All posts
|
||||||
home-recent = Recent posts
|
home-recent = Recent posts
|
||||||
home-tagline = guitar player - original songs, albums, and notes
|
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
|
home-no-posts = no published posts yet
|
||||||
blog-title = Blog
|
blog-title = Blog
|
||||||
blog-sub = published article(s)
|
blog-sub = published article(s)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ home-sub = novinky a aktuality.
|
|||||||
home-all-posts = Všetky príspevky
|
home-all-posts = Všetky príspevky
|
||||||
home-recent = Posledné príspevky
|
home-recent = Posledné príspevky
|
||||||
home-tagline = gitarista - autorské skladby, albumy a poznámky
|
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
|
home-no-posts = zatiaľ žiadne zverejnené príspevky
|
||||||
blog-title = Blog
|
blog-title = Blog
|
||||||
blog-sub = zverejnené články
|
blog-sub = zverejnené články
|
||||||
|
|||||||
@@ -17,9 +17,57 @@
|
|||||||
|
|
||||||
<div class="term-screen mb-6">
|
<div class="term-screen mb-6">
|
||||||
<p class="line out">→ {{ t(key="home-tagline", lang=lang | default(value='sk')) }}</p>
|
<p class="line out">→ {{ t(key="home-tagline", lang=lang | default(value='sk')) }}</p>
|
||||||
<p class="line out">{{ t(key="home-sections", lang=lang | default(value='sk')) }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if featured_track or featured_album %}
|
||||||
|
<section class="mb-8">
|
||||||
|
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>{{ t(key="home-picks", lang=lang | default(value='sk')) }}</p>
|
||||||
|
<div class="term-grid">
|
||||||
|
{% if featured_track %}
|
||||||
|
<article class="card">
|
||||||
|
<div class="term-head">
|
||||||
|
<span class="term-head-name">~/audio/tracks/{{ featured_track.slug }}</span>
|
||||||
|
<span class="term-head-meta term-tag is-green">{{ t(key="song", lang=lang | default(value='sk')) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-base">{{ featured_track.title }}</h2>
|
||||||
|
<div class="term-track">
|
||||||
|
<button type="button" class="uw-play btn btn-primary btn-sm"
|
||||||
|
data-src="/audio/tracks/{{ featured_track.id }}/stream" data-title="{{ featured_track.title }}">{{ t(key="audio-play", lang=lang | default(value='sk')) }}</button>
|
||||||
|
<span class="term-track-name"><span class="t-green">▸</span> {{ featured_track.title }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
{% if featured_album %}
|
||||||
|
<article class="card">
|
||||||
|
<div class="term-head">
|
||||||
|
<span class="term-head-name">~/audio/{{ featured_album.slug }}/</span>
|
||||||
|
<span class="term-head-meta term-tag is-purple">{{ t(key="album", lang=lang | default(value='sk')) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if featured_album.cover_image_id %}
|
||||||
|
<img src="/images/{{ featured_album.cover_image_id }}" alt="" class="mb-3">
|
||||||
|
{% endif %}
|
||||||
|
<h2 class="card-title text-base">{{ featured_album.title }}</h2>
|
||||||
|
{% if featured_album.artist %}
|
||||||
|
<p class="text-sm t-aqua">{{ featured_album.artist }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if featured_album.description %}
|
||||||
|
<p class="term-prose text-sm opacity-80">{{ featured_album.description }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex flex-wrap gap-2 pt-2">
|
||||||
|
<button type="button" class="uw-play-album-remote btn btn-primary btn-sm"
|
||||||
|
data-album-tracks-url="/audio/albums/{{ featured_album.slug }}/tracks">{{ t(key="audio-play", lang=lang | default(value='sk')) }}</button>
|
||||||
|
<a href="/audio/albums/{{ featured_album.slug }}" class="btn btn-outline btn-sm">{{ t(key="audio-open", lang=lang | default(value='sk')) }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>{{ t(key="home-recent", lang=lang | default(value='sk')) }} <span class="t-dim">({{ articles | length }})</span></p>
|
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>{{ t(key="home-recent", lang=lang | default(value='sk')) }} <span class="t-dim">({{ articles | length }})</span></p>
|
||||||
{% if articles | length > 0 %}
|
{% if articles | length > 0 %}
|
||||||
@@ -63,9 +111,57 @@
|
|||||||
|
|
||||||
<div class="term-screen mb-6">
|
<div class="term-screen mb-6">
|
||||||
<p class="line">{{ t(key="home-tagline", lang=lang | default(value='sk')) }}</p>
|
<p class="line">{{ t(key="home-tagline", lang=lang | default(value='sk')) }}</p>
|
||||||
<p class="line out">{{ t(key="home-sections", lang=lang | default(value='sk')) }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if featured_track or featured_album %}
|
||||||
|
<section class="mb-8">
|
||||||
|
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>{{ t(key="home-picks", lang=lang | default(value='sk')) }}</p>
|
||||||
|
<div class="term-grid">
|
||||||
|
{% if featured_track %}
|
||||||
|
<article class="card">
|
||||||
|
<div class="term-head">
|
||||||
|
<span class="term-head-name">~/audio/tracks/{{ featured_track.slug }}</span>
|
||||||
|
<span class="term-head-meta term-tag is-green">{{ t(key="song", lang=lang | default(value='sk')) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-base">{{ featured_track.title }}</h2>
|
||||||
|
<div class="term-track">
|
||||||
|
<button type="button" class="uw-play btn btn-primary btn-sm"
|
||||||
|
data-src="/audio/tracks/{{ featured_track.id }}/stream" data-title="{{ featured_track.title }}">{{ t(key="audio-play", lang=lang | default(value='sk')) }}</button>
|
||||||
|
<span class="term-track-name"><span class="t-green">▸</span> {{ featured_track.title }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
{% if featured_album %}
|
||||||
|
<article class="card">
|
||||||
|
<div class="term-head">
|
||||||
|
<span class="term-head-name">~/audio/{{ featured_album.slug }}/</span>
|
||||||
|
<span class="term-head-meta term-tag is-purple">{{ t(key="album", lang=lang | default(value='sk')) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if featured_album.cover_image_id %}
|
||||||
|
<img src="/images/{{ featured_album.cover_image_id }}" alt="" class="mb-3">
|
||||||
|
{% endif %}
|
||||||
|
<h2 class="card-title text-base">{{ featured_album.title }}</h2>
|
||||||
|
{% if featured_album.artist %}
|
||||||
|
<p class="text-sm t-aqua">{{ featured_album.artist }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if featured_album.description %}
|
||||||
|
<p class="term-prose text-sm opacity-80">{{ featured_album.description }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex flex-wrap gap-2 pt-2">
|
||||||
|
<button type="button" class="uw-play-album-remote btn btn-primary btn-sm"
|
||||||
|
data-album-tracks-url="/audio/albums/{{ featured_album.slug }}/tracks">{{ t(key="audio-play", lang=lang | default(value='sk')) }}</button>
|
||||||
|
<a href="/audio/albums/{{ featured_album.slug }}" class="btn btn-outline btn-sm">{{ t(key="audio-open", lang=lang | default(value='sk')) }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>{{ t(key="home-recent", lang=lang | default(value='sk')) }} <span class="t-dim">({{ articles | length }})</span></p>
|
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>{{ t(key="home-recent", lang=lang | default(value='sk')) }} <span class="t-dim">({{ articles | length }})</span></p>
|
||||||
{% if articles | length > 0 %}
|
{% if articles | length > 0 %}
|
||||||
|
|||||||
@@ -33,8 +33,17 @@ impl MigrationTrait for Migration {
|
|||||||
Table::create()
|
Table::create()
|
||||||
.table(BlogArticles::Table)
|
.table(BlogArticles::Table)
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(ColumnDef::new(BlogArticles::Id).uuid().not_null().primary_key())
|
.col(
|
||||||
.col(ColumnDef::new(BlogArticles::Title).string_len(500).not_null())
|
ColumnDef::new(BlogArticles::Id)
|
||||||
|
.uuid()
|
||||||
|
.not_null()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(BlogArticles::Title)
|
||||||
|
.string_len(500)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
.col(
|
.col(
|
||||||
ColumnDef::new(BlogArticles::Slug)
|
ColumnDef::new(BlogArticles::Slug)
|
||||||
.string_len(500)
|
.string_len(500)
|
||||||
@@ -42,7 +51,11 @@ impl MigrationTrait for Migration {
|
|||||||
.unique_key(),
|
.unique_key(),
|
||||||
)
|
)
|
||||||
.col(ColumnDef::new(BlogArticles::Content).text().not_null())
|
.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(
|
.col(
|
||||||
ColumnDef::new(BlogArticles::Published)
|
ColumnDef::new(BlogArticles::Published)
|
||||||
.boolean()
|
.boolean()
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ impl MigrationTrait for Migration {
|
|||||||
Table::create()
|
Table::create()
|
||||||
.table(AuditLogs::Table)
|
.table(AuditLogs::Table)
|
||||||
.if_not_exists()
|
.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::AdminUserId).integer().not_null())
|
||||||
.col(ColumnDef::new(AuditLogs::Action).string_len(100).not_null())
|
.col(ColumnDef::new(AuditLogs::Action).string_len(100).not_null())
|
||||||
.col(ColumnDef::new(AuditLogs::TargetType).string_len(50).null())
|
.col(ColumnDef::new(AuditLogs::TargetType).string_len(50).null())
|
||||||
|
|||||||
@@ -34,8 +34,17 @@ impl MigrationTrait for Migration {
|
|||||||
Table::create()
|
Table::create()
|
||||||
.table(AudioAlbums::Table)
|
.table(AudioAlbums::Table)
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(ColumnDef::new(AudioAlbums::Id).uuid().not_null().primary_key())
|
.col(
|
||||||
.col(ColumnDef::new(AudioAlbums::Title).string_len(500).not_null())
|
ColumnDef::new(AudioAlbums::Id)
|
||||||
|
.uuid()
|
||||||
|
.not_null()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(AudioAlbums::Title)
|
||||||
|
.string_len(500)
|
||||||
|
.not_null(),
|
||||||
|
)
|
||||||
.col(
|
.col(
|
||||||
ColumnDef::new(AudioAlbums::Slug)
|
ColumnDef::new(AudioAlbums::Slug)
|
||||||
.string_len(500)
|
.string_len(500)
|
||||||
|
|||||||
@@ -32,9 +32,18 @@ impl MigrationTrait for Migration {
|
|||||||
Table::create()
|
Table::create()
|
||||||
.table(AudioTracks::Table)
|
.table(AudioTracks::Table)
|
||||||
.if_not_exists()
|
.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::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::Slug).string_len(500).not_null())
|
||||||
.col(
|
.col(
|
||||||
ColumnDef::new(AudioTracks::AudioFileId)
|
ColumnDef::new(AudioTracks::AudioFileId)
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ impl MigrationTrait for Migration {
|
|||||||
Table::create()
|
Table::create()
|
||||||
.table(AudioTags::Table)
|
.table(AudioTags::Table)
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(ColumnDef::new(AudioTags::Id).uuid().not_null().primary_key())
|
.col(
|
||||||
|
ColumnDef::new(AudioTags::Id)
|
||||||
|
.uuid()
|
||||||
|
.not_null()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
.col(
|
.col(
|
||||||
ColumnDef::new(AudioTags::Name)
|
ColumnDef::new(AudioTags::Name)
|
||||||
.string_len(100)
|
.string_len(100)
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ impl MigrationTrait for Migration {
|
|||||||
Table::create()
|
Table::create()
|
||||||
.table(SitePages::Table)
|
.table(SitePages::Table)
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(ColumnDef::new(SitePages::Id).uuid().not_null().primary_key())
|
.col(
|
||||||
|
ColumnDef::new(SitePages::Id)
|
||||||
|
.uuid()
|
||||||
|
.not_null()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
.col(
|
.col(
|
||||||
ColumnDef::new(SitePages::Slug)
|
ColumnDef::new(SitePages::Slug)
|
||||||
.string_len(100)
|
.string_len(100)
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use crate::{
|
use crate::{controllers::admin, models::_entities::blog_articles};
|
||||||
controllers::admin,
|
|
||||||
models::_entities::blog_articles,
|
|
||||||
};
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use loco_rs::prelude::*;
|
use loco_rs::prelude::*;
|
||||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
|
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
|
||||||
@@ -195,7 +192,11 @@ async fn admin_update(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_delete(auth: auth::JWT, Path(id): Path<Uuid>, State(ctx): State<AppContext>) -> Result<Response> {
|
async fn admin_delete(
|
||||||
|
auth: auth::JWT,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
admin::current_admin(auth, &ctx).await?;
|
||||||
let article = find_article_by_id(&ctx, id).await?;
|
let article = find_article_by_id(&ctx, id).await?;
|
||||||
article.delete(&ctx.db).await?;
|
article.delete(&ctx.db).await?;
|
||||||
@@ -203,7 +204,11 @@ async fn admin_delete(auth: auth::JWT, Path(id): Path<Uuid>, State(ctx): State<A
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_publish(auth: auth::JWT, Path(id): Path<Uuid>, State(ctx): State<AppContext>) -> Result<Response> {
|
async fn admin_publish(
|
||||||
|
auth: auth::JWT,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
admin::current_admin(auth, &ctx).await?;
|
||||||
let mut article = find_article_by_id(&ctx, id).await?.into_active_model();
|
let mut article = find_article_by_id(&ctx, id).await?.into_active_model();
|
||||||
article.published = Set(true);
|
article.published = Set(true);
|
||||||
@@ -213,7 +218,11 @@ async fn admin_publish(auth: auth::JWT, Path(id): Path<Uuid>, State(ctx): State<
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_unpublish(auth: auth::JWT, Path(id): Path<Uuid>, State(ctx): State<AppContext>) -> Result<Response> {
|
async fn admin_unpublish(
|
||||||
|
auth: auth::JWT,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
admin::current_admin(auth, &ctx).await?;
|
||||||
let mut article = find_article_by_id(&ctx, id).await?.into_active_model();
|
let mut article = find_article_by_id(&ctx, id).await?.into_active_model();
|
||||||
article.published = Set(false);
|
article.published = Set(false);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
controllers::{admin, auth as auth_controller, i18n::current_lang},
|
controllers::{admin, auth as auth_controller, i18n::current_lang},
|
||||||
models::{
|
models::{
|
||||||
_entities::{blog_articles, site_pages},
|
_entities::{audio_albums, audio_tracks, blog_articles, site_pages},
|
||||||
users::{self, LoginParams},
|
users::{self, LoginParams},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -9,7 +9,8 @@ use axum_extra::extract::cookie::CookieJar;
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use loco_rs::prelude::*;
|
use loco_rs::prelude::*;
|
||||||
use sea_orm::{
|
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::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@@ -59,7 +60,9 @@ fn published_at_for(published: bool) -> Option<chrono::DateTime<chrono::FixedOff
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_checked(value: &Option<String>) -> bool {
|
fn is_checked(value: &Option<String>) -> 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<String>) -> Option<String> {
|
fn normalize_empty(value: Option<String>) -> Option<String> {
|
||||||
@@ -119,11 +122,31 @@ async fn home(
|
|||||||
.all(&ctx.db)
|
.all(&ctx.db)
|
||||||
.await?;
|
.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(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"home/index.html",
|
"home/index.html",
|
||||||
json!({
|
json!({
|
||||||
"articles": articles,
|
"articles": articles,
|
||||||
|
"featured_track": featured_track,
|
||||||
|
"featured_album": featured_album,
|
||||||
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
||||||
"lang": current_lang(&jar),
|
"lang": current_lang(&jar),
|
||||||
}),
|
}),
|
||||||
@@ -340,7 +363,11 @@ async fn admin_article_new(
|
|||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
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]
|
#[debug_handler]
|
||||||
@@ -445,5 +472,8 @@ pub fn routes() -> Routes {
|
|||||||
.add("/admin/blog/articles", post(admin_article_create))
|
.add("/admin/blog/articles", post(admin_article_create))
|
||||||
.add("/admin/blog/articles/{id}/edit", get(admin_article_edit))
|
.add("/admin/blog/articles/{id}/edit", get(admin_article_edit))
|
||||||
.add("/admin/blog/articles/{id}", post(admin_article_update))
|
.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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ pub struct LangForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_lang(jar: &axum_extra::extract::cookie::CookieJar) -> String {
|
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(),
|
Some(ref lang) if lang == "en" => "en".to_string(),
|
||||||
_ => "sk".to_string(),
|
_ => "sk".to_string(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,9 @@ async fn read_multipart_file(mut multipart: Multipart, max_bytes: usize) -> Resu
|
|||||||
return Ok(data);
|
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(
|
async fn read_track_upload(
|
||||||
@@ -254,7 +256,11 @@ async fn read_track_upload(
|
|||||||
match name.as_str() {
|
match name.as_str() {
|
||||||
"title" => title = normalize_empty(Some(value)),
|
"title" => title = normalize_empty(Some(value)),
|
||||||
"track_number" => {
|
"track_number" => {
|
||||||
track_number = value.trim().parse::<i32>().ok().filter(|number| *number > 0)
|
track_number = value
|
||||||
|
.trim()
|
||||||
|
.parse::<i32>()
|
||||||
|
.ok()
|
||||||
|
.filter(|number| *number > 0)
|
||||||
}
|
}
|
||||||
"featured" => featured = value == "on" || value == "true" || value == "1",
|
"featured" => featured = value == "on" || value == "true" || value == "1",
|
||||||
"published" => published = 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() {
|
if data.is_empty() {
|
||||||
return Err(Error::BadRequest("empty file upload".to_string()));
|
return Err(Error::BadRequest("empty file upload".to_string()));
|
||||||
}
|
}
|
||||||
@@ -356,7 +363,11 @@ async fn unique_album_slug(ctx: &AppContext, title: &str) -> Result<String> {
|
|||||||
Ok(slug)
|
Ok(slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unique_track_slug(ctx: &AppContext, album_id: Option<Uuid>, title: &str) -> Result<String> {
|
async fn unique_track_slug(
|
||||||
|
ctx: &AppContext,
|
||||||
|
album_id: Option<Uuid>,
|
||||||
|
title: &str,
|
||||||
|
) -> Result<String> {
|
||||||
let base = slugify(title);
|
let base = slugify(title);
|
||||||
let mut slug = base.clone();
|
let mut slug = base.clone();
|
||||||
let mut suffix = 2;
|
let mut suffix = 2;
|
||||||
@@ -411,7 +422,12 @@ async fn track_by_id(ctx: &AppContext, id: Uuid) -> Result<audio_tracks::Model>
|
|||||||
.ok_or_else(|| Error::NotFound)
|
.ok_or_else(|| Error::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn store_upload(ctx: &AppContext, folder: &str, extension: &str, data: Vec<u8>) -> Result<String> {
|
async fn store_upload(
|
||||||
|
ctx: &AppContext,
|
||||||
|
folder: &str,
|
||||||
|
extension: &str,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<String> {
|
||||||
let filename = format!("{}.{}", Uuid::new_v4(), extension);
|
let filename = format!("{}.{}", Uuid::new_v4(), extension);
|
||||||
let key = format!("{folder}/{filename}");
|
let key = format!("{folder}/{filename}");
|
||||||
ctx.storage
|
ctx.storage
|
||||||
@@ -421,7 +437,11 @@ async fn store_upload(ctx: &AppContext, folder: &str, extension: &str, data: Vec
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn image_upload(auth: auth::JWT, State(ctx): State<AppContext>, multipart: Multipart) -> Result<Response> {
|
async fn image_upload(
|
||||||
|
auth: auth::JWT,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
multipart: Multipart,
|
||||||
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
admin::current_admin(auth, &ctx).await?;
|
||||||
let data = read_multipart_file(multipart, IMAGE_MAX_BYTES).await?;
|
let data = read_multipart_file(multipart, IMAGE_MAX_BYTES).await?;
|
||||||
let extension = detect_image_extension(&data)?;
|
let extension = detect_image_extension(&data)?;
|
||||||
@@ -436,7 +456,10 @@ async fn image_upload(auth: auth::JWT, State(ctx): State<AppContext>, multipart:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn image_serve(Path(filename): Path<String>, State(ctx): State<AppContext>) -> Result<Response> {
|
async fn image_serve(
|
||||||
|
Path(filename): Path<String>,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
) -> Result<Response> {
|
||||||
let filename = safe_filename(&filename)?;
|
let filename = safe_filename(&filename)?;
|
||||||
let extension = filename.rsplit('.').next().unwrap_or("");
|
let extension = filename.rsplit('.').next().unwrap_or("");
|
||||||
let key = format!("{IMAGE_STORAGE_DIR}/{filename}");
|
let key = format!("{IMAGE_STORAGE_DIR}/{filename}");
|
||||||
@@ -449,7 +472,11 @@ async fn image_serve(Path(filename): Path<String>, State(ctx): State<AppContext>
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn audio_upload(auth: auth::JWT, State(ctx): State<AppContext>, multipart: Multipart) -> Result<Response> {
|
async fn audio_upload(
|
||||||
|
auth: auth::JWT,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
multipart: Multipart,
|
||||||
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
admin::current_admin(auth, &ctx).await?;
|
||||||
let data = read_multipart_file(multipart, AUDIO_MAX_BYTES).await?;
|
let data = read_multipart_file(multipart, AUDIO_MAX_BYTES).await?;
|
||||||
let extension = detect_audio_extension(&data)?;
|
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?;
|
let track = track_by_id(&ctx, params.track_id).await?;
|
||||||
|
|
||||||
if track.album_id.is_some() {
|
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();
|
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 (data, title, track_number, featured, published) = read_track_upload(multipart).await?;
|
||||||
let extension = detect_audio_extension(&data)?;
|
let extension = detect_audio_extension(&data)?;
|
||||||
let filename = store_upload(ctx, AUDIO_STORAGE_DIR, extension, data).await?;
|
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 {
|
audio_tracks::ActiveModel {
|
||||||
id: Set(Uuid::new_v4()),
|
id: Set(Uuid::new_v4()),
|
||||||
@@ -886,7 +919,10 @@ async fn admin_track_delete(
|
|||||||
let album_id = track.album_id;
|
let album_id = track.album_id;
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.storage
|
.storage
|
||||||
.delete(StdPath::new(&format!("{AUDIO_STORAGE_DIR}/{}", track.audio_file_id)))
|
.delete(StdPath::new(&format!(
|
||||||
|
"{AUDIO_STORAGE_DIR}/{}",
|
||||||
|
track.audio_file_id
|
||||||
|
)))
|
||||||
.await;
|
.await;
|
||||||
track.delete(&ctx.db).await?;
|
track.delete(&ctx.db).await?;
|
||||||
if let Some(album_id) = album_id {
|
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<Response> {
|
async fn stream_audio_file(
|
||||||
|
config: &Config,
|
||||||
|
filename: &str,
|
||||||
|
headers: &HeaderMap,
|
||||||
|
) -> Result<Response> {
|
||||||
let filename = safe_filename(filename)?;
|
let filename = safe_filename(filename)?;
|
||||||
let path = uploads_root(config)?.join(AUDIO_STORAGE_DIR).join(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 total_len = file.metadata().await?.len();
|
||||||
let extension = filename.rsplit('.').next().unwrap_or("mp3");
|
let extension = filename.rsplit('.').next().unwrap_or("mp3");
|
||||||
let content_type = audio_content_type(extension);
|
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 suffix_range = start.is_empty();
|
||||||
let start = if suffix_range {
|
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)
|
total_len.saturating_sub(suffix)
|
||||||
} else {
|
} else {
|
||||||
u64::from_str(start).map_err(|_| Error::BadRequest("invalid range header".to_string()))?
|
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 {
|
pub fn routes() -> Routes {
|
||||||
Routes::new()
|
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("/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/stream/{filename}", get(raw_audio_stream))
|
||||||
.add("/audio/albums", get(public_albums))
|
.add("/audio/albums", get(public_albums))
|
||||||
.add("/audio/albums/{slug}", get(public_album))
|
.add("/audio/albums/{slug}", get(public_album))
|
||||||
@@ -1050,16 +1099,43 @@ pub fn routes() -> Routes {
|
|||||||
.add("/audio/tracks/{id}/stream", get(track_stream))
|
.add("/audio/tracks/{id}/stream", get(track_stream))
|
||||||
.add("/admin/audio/albums", get(admin_albums))
|
.add("/admin/audio/albums", get(admin_albums))
|
||||||
.add("/admin/audio/albums/create", get(admin_album_new))
|
.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", get(admin_tracks))
|
||||||
.add("/admin/audio/tracks/upload", get(admin_song_upload_form))
|
.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(
|
||||||
.add("/admin/audio/albums/{album_id}/tracks", get(admin_album_tracks))
|
"/admin/audio/tracks/upload-file",
|
||||||
.add("/admin/audio/albums/{album_id}/tracks/add", post(admin_album_add_track))
|
post(admin_song_upload).layer(DefaultBodyLimit::max(AUDIO_MAX_BYTES + 1024 * 1024)),
|
||||||
.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(
|
||||||
.add("/admin/audio/tracks/{id}/publish", post(admin_track_publish))
|
"/admin/audio/albums/{album_id}/tracks",
|
||||||
.add("/admin/audio/tracks/{id}/unpublish", post(admin_track_unpublish))
|
get(admin_album_tracks),
|
||||||
.add("/admin/audio/tracks/{id}/remove-from-album", post(admin_track_remove_from_album))
|
)
|
||||||
|
.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))
|
.add("/admin/audio/tracks/{id}/delete", post(admin_track_delete))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
pub mod admin;
|
pub mod admin;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod blog;
|
pub mod blog;
|
||||||
pub mod i18n;
|
|
||||||
pub mod frontend;
|
pub mod frontend;
|
||||||
|
pub mod i18n;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
pub mod pages;
|
pub mod pages;
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use crate::{
|
use crate::{controllers::admin, models::_entities::site_pages};
|
||||||
controllers::admin,
|
|
||||||
models::_entities::site_pages,
|
|
||||||
};
|
|
||||||
use loco_rs::prelude::*;
|
use loco_rs::prelude::*;
|
||||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
|
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
pub use super::_entities::audio_albums::{ActiveModel, Entity, Model};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
pub use super::_entities::audio_albums::{ActiveModel, Model, Entity};
|
|
||||||
pub type AudioAlbums = Entity;
|
pub type AudioAlbums = Entity;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
pub use super::_entities::audio_tags::{ActiveModel, Entity, Model};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
pub use super::_entities::audio_tags::{ActiveModel, Model, Entity};
|
|
||||||
pub type AudioTags = Entity;
|
pub type AudioTags = Entity;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
pub use super::_entities::audio_track_tags::{ActiveModel, Entity, Model};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
pub use super::_entities::audio_track_tags::{ActiveModel, Model, Entity};
|
|
||||||
pub type AudioTrackTags = Entity;
|
pub type AudioTrackTags = Entity;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
pub use super::_entities::audio_tracks::{ActiveModel, Entity, Model};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
pub use super::_entities::audio_tracks::{ActiveModel, Model, Entity};
|
|
||||||
pub type AudioTracks = Entity;
|
pub type AudioTracks = Entity;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
pub use super::_entities::audit_logs::{ActiveModel, Entity, Model};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
pub use super::_entities::audit_logs::{ActiveModel, Model, Entity};
|
|
||||||
pub type AuditLogs = Entity;
|
pub type AuditLogs = Entity;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
pub use super::_entities::blog_articles::{ActiveModel, Entity, Model};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
pub use super::_entities::blog_articles::{ActiveModel, Model, Entity};
|
|
||||||
pub type BlogArticles = Entity;
|
pub type BlogArticles = Entity;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
pub mod _entities;
|
pub mod _entities;
|
||||||
pub mod users;
|
pub mod audio_albums;
|
||||||
pub mod audio_tags;
|
pub mod audio_tags;
|
||||||
pub mod audio_tracks;
|
|
||||||
pub mod audio_track_tags;
|
pub mod audio_track_tags;
|
||||||
|
pub mod audio_tracks;
|
||||||
pub mod audit_logs;
|
pub mod audit_logs;
|
||||||
pub mod blog_articles;
|
pub mod blog_articles;
|
||||||
pub mod audio_albums;
|
|
||||||
pub mod site_pages;
|
pub mod site_pages;
|
||||||
|
pub mod users;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
|
||||||
pub use super::_entities::site_pages::{ActiveModel, Entity, Model};
|
pub use super::_entities::site_pages::{ActiveModel, Entity, Model};
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
pub type SitePages = Entity;
|
pub type SitePages = Entity;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|||||||
Reference in New Issue
Block a user