about page
This commit is contained in:
@@ -12,6 +12,7 @@ mod m20260517_000007_audio_tags;
|
||||
mod m20260517_000008_audio_track_tags;
|
||||
mod m20260517_000009_simple_constraints;
|
||||
mod m20260517_000010_drop_user_roles;
|
||||
mod m20260517_000011_site_pages;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -30,6 +31,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260517_000008_audio_track_tags::Migration),
|
||||
Box::new(m20260517_000009_simple_constraints::Migration),
|
||||
Box::new(m20260517_000010_drop_user_roles::Migration),
|
||||
Box::new(m20260517_000011_site_pages::Migration),
|
||||
// inject-above (do not remove this comment)
|
||||
]
|
||||
}
|
||||
|
||||
65
migration/src/m20260517_000011_site_pages.rs
Normal file
65
migration/src/m20260517_000011_site_pages.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use sea_orm_migration::{prelude::*, sea_orm::ConnectionTrait, sea_query::Expr};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum SitePages {
|
||||
Table,
|
||||
Id,
|
||||
Slug,
|
||||
Title,
|
||||
Content,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.create_table(
|
||||
Table::create()
|
||||
.table(SitePages::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(SitePages::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(SitePages::Slug)
|
||||
.string_len(100)
|
||||
.not_null()
|
||||
.unique_key(),
|
||||
)
|
||||
.col(ColumnDef::new(SitePages::Title).string_len(500).not_null())
|
||||
.col(ColumnDef::new(SitePages::Content).text().not_null())
|
||||
.col(
|
||||
ColumnDef::new(SitePages::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(SitePages::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
m.get_connection()
|
||||
.execute_unprepared(
|
||||
"INSERT INTO site_pages (id, slug, title, content, created_at, updated_at) \
|
||||
VALUES (gen_random_uuid(), 'about', 'About', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) \
|
||||
ON CONFLICT (slug) DO NOTHING",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.drop_table(Table::drop().table(SitePages::Table).to_owned())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ impl Hooks for App {
|
||||
.add_route(controllers::auth::routes())
|
||||
.add_route(controllers::admin::routes())
|
||||
.add_route(controllers::blog::routes())
|
||||
.add_route(controllers::pages::routes())
|
||||
}
|
||||
async fn connect_workers(ctx: &AppContext, queue: &Queue) -> Result<()> {
|
||||
queue.register(DownloadWorker::build(ctx)).await?;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod admin;
|
||||
pub mod auth;
|
||||
pub mod blog;
|
||||
pub mod pages;
|
||||
|
||||
89
src/controllers/pages.rs
Normal file
89
src/controllers/pages.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use crate::{
|
||||
controllers::admin,
|
||||
models::_entities::site_pages,
|
||||
};
|
||||
use loco_rs::prelude::*;
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
const ABOUT_SLUG: &str = "about";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AboutParams {
|
||||
title: String,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct PageResponse {
|
||||
id: Uuid,
|
||||
slug: String,
|
||||
title: String,
|
||||
content: String,
|
||||
updated_at: chrono::DateTime<chrono::FixedOffset>,
|
||||
}
|
||||
|
||||
impl From<site_pages::Model> for PageResponse {
|
||||
fn from(page: site_pages::Model) -> Self {
|
||||
Self {
|
||||
id: page.id,
|
||||
slug: page.slug,
|
||||
title: page.title,
|
||||
content: page.content,
|
||||
updated_at: page.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_about(ctx: &AppContext) -> Result<site_pages::Model> {
|
||||
site_pages::Entity::find()
|
||||
.filter(site_pages::Column::Slug.eq(ABOUT_SLUG))
|
||||
.one(&ctx.db)
|
||||
.await?
|
||||
.ok_or_else(|| Error::NotFound)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
async fn about(State(ctx): State<AppContext>) -> Result<Response> {
|
||||
format::json(PageResponse::from(find_about(&ctx).await?))
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
async fn update_about(
|
||||
auth: auth::JWT,
|
||||
State(ctx): State<AppContext>,
|
||||
Json(params): Json<AboutParams>,
|
||||
) -> Result<Response> {
|
||||
admin::current_admin(auth, &ctx).await?;
|
||||
|
||||
let page = match find_about(&ctx).await {
|
||||
Ok(page) => {
|
||||
let mut page = page.into_active_model();
|
||||
page.title = Set(params.title);
|
||||
page.content = Set(params.content);
|
||||
page.update(&ctx.db).await?
|
||||
}
|
||||
Err(Error::NotFound) => {
|
||||
site_pages::ActiveModel {
|
||||
id: Set(Uuid::new_v4()),
|
||||
slug: Set(ABOUT_SLUG.to_string()),
|
||||
title: Set(params.title),
|
||||
content: Set(params.content),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&ctx.db)
|
||||
.await?
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
format::json(PageResponse::from(page))
|
||||
}
|
||||
|
||||
pub fn routes() -> Routes {
|
||||
Routes::new()
|
||||
.prefix("/api")
|
||||
.add("/about", get(about))
|
||||
.add("/admin/about", put(update_about))
|
||||
}
|
||||
@@ -8,4 +8,5 @@ pub mod audio_track_tags;
|
||||
pub mod audio_tracks;
|
||||
pub mod audit_logs;
|
||||
pub mod blog_articles;
|
||||
pub mod site_pages;
|
||||
pub mod users;
|
||||
|
||||
@@ -6,4 +6,5 @@ pub use super::audio_track_tags::Entity as AudioTrackTags;
|
||||
pub use super::audio_tracks::Entity as AudioTracks;
|
||||
pub use super::audit_logs::Entity as AuditLogs;
|
||||
pub use super::blog_articles::Entity as BlogArticles;
|
||||
pub use super::site_pages::Entity as SitePages;
|
||||
pub use super::users::Entity as Users;
|
||||
|
||||
21
src/models/_entities/site_pages.rs
Normal file
21
src/models/_entities/site_pages.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.20
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "site_pages")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
#[sea_orm(unique)]
|
||||
pub slug: String,
|
||||
pub title: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub content: String,
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
@@ -6,3 +6,4 @@ pub mod audio_track_tags;
|
||||
pub mod audit_logs;
|
||||
pub mod blog_articles;
|
||||
pub mod audio_albums;
|
||||
pub mod site_pages;
|
||||
|
||||
23
src/models/site_pages.rs
Normal file
23
src/models/site_pages.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
pub use super::_entities::site_pages::{ActiveModel, Entity, Model};
|
||||
pub type SitePages = Entity;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ActiveModelBehavior for ActiveModel {
|
||||
async fn before_save<C>(self, _db: &C, insert: bool) -> std::result::Result<Self, DbErr>
|
||||
where
|
||||
C: ConnectionTrait,
|
||||
{
|
||||
if !insert && self.updated_at.is_unchanged() {
|
||||
let mut this = self;
|
||||
this.updated_at = sea_orm::ActiveValue::Set(chrono::Utc::now().into());
|
||||
Ok(this)
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {}
|
||||
impl ActiveModel {}
|
||||
impl Entity {}
|
||||
Reference in New Issue
Block a user