Parent and child posts
This commit is contained in:
parent
04f888c323
commit
54379b98e0
16 changed files with 334 additions and 58 deletions
|
|
@ -4,8 +4,11 @@ Sam's small image board. Currently a WIP.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
|
- [ ] Parent posts (including tags and stuff)
|
||||||
|
- [ ] Edit tags in the middle of input
|
||||||
- [ ] Pools
|
- [ ] Pools
|
||||||
- [ ] Video support
|
- [ ] Video support
|
||||||
|
- [ ] Cleanup/fixup background tasks
|
||||||
- [ ] CSS
|
- [ ] CSS
|
||||||
- [ ] CLI, env vars, logging...
|
- [ ] CLI, env vars, logging...
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,29 @@ impl MigrationTrait for Migration {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(SameyTag::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(pk_auto(SameyTag::Id))
|
||||||
|
.col(string_len(SameyTag::Name, 100))
|
||||||
|
.col(string_len_uniq(SameyTag::NormalizedName, 100))
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(SameyPool::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(pk_auto(SameyPool::Id))
|
||||||
|
.col(string_len_uniq(SameyPool::Name, 100))
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.create_table(
|
.create_table(
|
||||||
Table::create()
|
Table::create()
|
||||||
|
|
@ -98,18 +121,6 @@ impl MigrationTrait for Migration {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
manager
|
|
||||||
.create_table(
|
|
||||||
Table::create()
|
|
||||||
.table(SameyTag::Table)
|
|
||||||
.if_not_exists()
|
|
||||||
.col(pk_auto(SameyTag::Id))
|
|
||||||
.col(string_len(SameyTag::Name, 100))
|
|
||||||
.col(string_len_uniq(SameyTag::NormalizedName, 100))
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.create_table(
|
.create_table(
|
||||||
Table::create()
|
Table::create()
|
||||||
|
|
@ -142,6 +153,39 @@ impl MigrationTrait for Migration {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(SameyPoolPost::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(pk_auto(SameyPoolPost::Id))
|
||||||
|
.col(integer(SameyPoolPost::PoolId))
|
||||||
|
.col(integer(SameyPoolPost::PostId))
|
||||||
|
.col(float(SameyPoolPost::Position))
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKeyCreateStatement::new()
|
||||||
|
.name("fk-samey_pool_post-samey_pool-pool_id")
|
||||||
|
.from(SameyPoolPost::Table, SameyPoolPost::PoolId)
|
||||||
|
.to(SameyPool::Table, SameyPool::Id)
|
||||||
|
.on_delete(ForeignKeyAction::Cascade),
|
||||||
|
)
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKeyCreateStatement::new()
|
||||||
|
.name("fk-samey_pool_post-samey_post-post_id")
|
||||||
|
.from(SameyPoolPost::Table, SameyPoolPost::PostId)
|
||||||
|
.to(SameyPost::Table, SameyPost::Id)
|
||||||
|
.on_delete(ForeignKeyAction::Cascade),
|
||||||
|
)
|
||||||
|
.index(
|
||||||
|
Index::create()
|
||||||
|
.unique()
|
||||||
|
.col(SameyPoolPost::PoolId)
|
||||||
|
.col(SameyPoolPost::PostId),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,11 +193,11 @@ impl MigrationTrait for Migration {
|
||||||
// Replace the sample below with your own migration scripts
|
// Replace the sample below with your own migration scripts
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(SameyTagPost::Table).to_owned())
|
.drop_table(Table::drop().table(SameyPoolPost::Table).to_owned())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(SameyTag::Table).to_owned())
|
.drop_table(Table::drop().table(SameyTagPost::Table).to_owned())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
manager
|
manager
|
||||||
|
|
@ -164,6 +208,14 @@ impl MigrationTrait for Migration {
|
||||||
.drop_table(Table::drop().table(SameyPost::Table).to_owned())
|
.drop_table(Table::drop().table(SameyPost::Table).to_owned())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(SameyPool::Table).to_owned())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(SameyTag::Table).to_owned())
|
||||||
|
.await?;
|
||||||
|
|
||||||
manager
|
manager
|
||||||
.drop_table(Table::drop().table(SameyUser::Table).to_owned())
|
.drop_table(Table::drop().table(SameyUser::Table).to_owned())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -255,3 +307,21 @@ enum SameyTagPost {
|
||||||
TagId,
|
TagId,
|
||||||
PostId,
|
PostId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
enum SameyPool {
|
||||||
|
#[sea_orm(iden = "samey_pool")]
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
enum SameyPoolPost {
|
||||||
|
#[sea_orm(iden = "samey_pool_post")]
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
PoolId,
|
||||||
|
PostId,
|
||||||
|
Position,
|
||||||
|
}
|
||||||
|
|
|
||||||
21
src/auth.rs
21
src/auth.rs
|
|
@ -123,7 +123,7 @@ impl SessionStore for SessionStorage {
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
.collect(),
|
.collect(),
|
||||||
)),
|
)),
|
||||||
expiry_date: Set(record.expiry_date.unix_timestamp().to_string()),
|
expiry_date: Set(record.expiry_date.unix_timestamp()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.exec(&self.db)
|
.exec(&self.db)
|
||||||
|
|
@ -141,7 +141,7 @@ impl SessionStore for SessionStorage {
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
.collect(),
|
.collect(),
|
||||||
)),
|
)),
|
||||||
expiry_date: Set(record.expiry_date.unix_timestamp().to_string()),
|
expiry_date: Set(record.expiry_date.unix_timestamp()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.filter(samey_session::Column::SessionId.eq(record.id.to_string()))
|
.filter(samey_session::Column::SessionId.eq(record.id.to_string()))
|
||||||
|
|
@ -175,20 +175,9 @@ impl SessionStore for SessionStorage {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
.collect(),
|
.collect(),
|
||||||
expiry_date: match session.expiry_date.parse() {
|
expiry_date: OffsetDateTime::from_unix_timestamp(session.expiry_date).map_err(
|
||||||
Ok(timestamp) => {
|
|_| session_store::Error::Backend("Invalid timestamp for expiry date".into()),
|
||||||
OffsetDateTime::from_unix_timestamp(timestamp).map_err(|_| {
|
)?,
|
||||||
session_store::Error::Backend(
|
|
||||||
"Invalid timestamp for expiry date".into(),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
return Err(session_store::Error::Backend(
|
|
||||||
"Failed to parse session expiry date".into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
|
||||||
|
pub mod samey_pool;
|
||||||
|
pub mod samey_pool_post;
|
||||||
pub mod samey_post;
|
pub mod samey_post;
|
||||||
pub mod samey_post_source;
|
pub mod samey_post_source;
|
||||||
pub mod samey_session;
|
pub mod samey_session;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.8
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.8
|
||||||
|
|
||||||
|
pub use super::samey_pool::Entity as SameyPool;
|
||||||
|
pub use super::samey_pool_post::Entity as SameyPoolPost;
|
||||||
pub use super::samey_post::Entity as SameyPost;
|
pub use super::samey_post::Entity as SameyPost;
|
||||||
pub use super::samey_post_source::Entity as SameyPostSource;
|
pub use super::samey_post_source::Entity as SameyPostSource;
|
||||||
pub use super::samey_session::Entity as SameySession;
|
pub use super::samey_session::Entity as SameySession;
|
||||||
|
|
|
||||||
26
src/entities/samey_pool.rs
Normal file
26
src/entities/samey_pool.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.8
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "samey_pool")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::samey_pool_post::Entity")]
|
||||||
|
SameyPoolPost,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::samey_pool_post::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SameyPoolPost.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
48
src/entities/samey_pool_post.rs
Normal file
48
src/entities/samey_pool_post.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.8
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "samey_pool_post")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub pool_id: i32,
|
||||||
|
pub post_id: i32,
|
||||||
|
#[sea_orm(column_type = "Float")]
|
||||||
|
pub position: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::samey_pool::Entity",
|
||||||
|
from = "Column::PoolId",
|
||||||
|
to = "super::samey_pool::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
SameyPool,
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::samey_post::Entity",
|
||||||
|
from = "Column::PostId",
|
||||||
|
to = "super::samey_post::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
SameyPost,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::samey_pool::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SameyPool.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::samey_post::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SameyPost.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
@ -24,6 +24,8 @@ pub struct Model {
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::samey_pool_post::Entity")]
|
||||||
|
SameyPoolPost,
|
||||||
#[sea_orm(
|
#[sea_orm(
|
||||||
belongs_to = "Entity",
|
belongs_to = "Entity",
|
||||||
from = "Column::ParentId",
|
from = "Column::ParentId",
|
||||||
|
|
@ -46,6 +48,12 @@ pub enum Relation {
|
||||||
SameyUser,
|
SameyUser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Related<super::samey_pool_post::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::SameyPoolPost.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Related<super::samey_post_source::Entity> for Entity {
|
impl Related<super::samey_post_source::Entity> for Entity {
|
||||||
fn to() -> RelationDef {
|
fn to() -> RelationDef {
|
||||||
Relation::SameyPostSource.def()
|
Relation::SameyPostSource.def()
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ use sea_orm::entity::prelude::*;
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
#[sea_orm(unique)]
|
||||||
pub session_id: String,
|
pub session_id: String,
|
||||||
pub data: Json,
|
pub data: Json,
|
||||||
pub expiry_date: String,
|
pub expiry_date: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
||||||
37
src/query.rs
37
src/query.rs
|
|
@ -16,16 +16,17 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, FromQueryResult)]
|
#[derive(Debug, FromQueryResult)]
|
||||||
pub(crate) struct SearchPost {
|
pub(crate) struct PostOverview {
|
||||||
pub(crate) id: i32,
|
pub(crate) id: i32,
|
||||||
pub(crate) thumbnail: String,
|
pub(crate) thumbnail: String,
|
||||||
pub(crate) tags: String,
|
pub(crate) tags: String,
|
||||||
|
pub(crate) rating: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn search_posts(
|
pub(crate) fn search_posts(
|
||||||
tags: Option<&Vec<&str>>,
|
tags: Option<&Vec<&str>>,
|
||||||
user: Option<User>,
|
user: Option<&User>,
|
||||||
) -> Selector<SelectModel<SearchPost>> {
|
) -> Selector<SelectModel<PostOverview>> {
|
||||||
let mut include_tags = HashSet::<String>::new();
|
let mut include_tags = HashSet::<String>::new();
|
||||||
let mut exclude_tags = HashSet::<String>::new();
|
let mut exclude_tags = HashSet::<String>::new();
|
||||||
let mut include_ratings = HashSet::<String>::new();
|
let mut include_ratings = HashSet::<String>::new();
|
||||||
|
|
@ -47,11 +48,12 @@ pub(crate) fn search_posts(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut query = if include_tags.is_empty() && exclude_tags.is_empty() {
|
let query = if include_tags.is_empty() && exclude_tags.is_empty() {
|
||||||
let mut query = SameyPost::find()
|
let mut query = SameyPost::find()
|
||||||
.select_only()
|
.select_only()
|
||||||
.column(samey_post::Column::Id)
|
.column(samey_post::Column::Id)
|
||||||
.column(samey_post::Column::Thumbnail)
|
.column(samey_post::Column::Thumbnail)
|
||||||
|
.column(samey_post::Column::Rating)
|
||||||
.column_as(
|
.column_as(
|
||||||
Expr::cust("GROUP_CONCAT(\"samey_tag\".\"name\", ' ')"),
|
Expr::cust("GROUP_CONCAT(\"samey_tag\".\"name\", ' ')"),
|
||||||
"tags",
|
"tags",
|
||||||
|
|
@ -73,6 +75,7 @@ pub(crate) fn search_posts(
|
||||||
.select_only()
|
.select_only()
|
||||||
.column(samey_post::Column::Id)
|
.column(samey_post::Column::Id)
|
||||||
.column(samey_post::Column::Thumbnail)
|
.column(samey_post::Column::Thumbnail)
|
||||||
|
.column(samey_post::Column::Rating)
|
||||||
.column_as(
|
.column_as(
|
||||||
Expr::cust("GROUP_CONCAT(\"samey_tag\".\"name\", ' ')"),
|
Expr::cust("GROUP_CONCAT(\"samey_tag\".\"name\", ' ')"),
|
||||||
"tags",
|
"tags",
|
||||||
|
|
@ -130,20 +133,10 @@ pub(crate) fn search_posts(
|
||||||
query
|
query
|
||||||
};
|
};
|
||||||
|
|
||||||
query = match user {
|
filter_by_user(query, user)
|
||||||
None => query.filter(samey_post::Column::IsPublic.into_simple_expr()),
|
|
||||||
Some(user) if !user.is_admin => query.filter(
|
|
||||||
Condition::any()
|
|
||||||
.add(samey_post::Column::IsPublic.into_simple_expr())
|
|
||||||
.add(samey_post::Column::UploaderId.eq(user.id)),
|
|
||||||
),
|
|
||||||
_ => query,
|
|
||||||
};
|
|
||||||
|
|
||||||
query
|
|
||||||
.group_by(samey_post::Column::Id)
|
.group_by(samey_post::Column::Id)
|
||||||
.order_by_desc(samey_post::Column::Id)
|
.order_by_desc(samey_post::Column::Id)
|
||||||
.into_model::<SearchPost>()
|
.into_model::<PostOverview>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_tags_for_post(post_id: i32) -> Select<SameyTag> {
|
pub(crate) fn get_tags_for_post(post_id: i32) -> Select<SameyTag> {
|
||||||
|
|
@ -152,3 +145,15 @@ pub(crate) fn get_tags_for_post(post_id: i32) -> Select<SameyTag> {
|
||||||
.filter(samey_tag_post::Column::PostId.eq(post_id))
|
.filter(samey_tag_post::Column::PostId.eq(post_id))
|
||||||
.order_by_asc(samey_tag::Column::Name)
|
.order_by_asc(samey_tag::Column::Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn filter_by_user(query: Select<SameyPost>, user: Option<&User>) -> Select<SameyPost> {
|
||||||
|
match user {
|
||||||
|
None => query.filter(samey_post::Column::IsPublic.into_simple_expr()),
|
||||||
|
Some(user) if !user.is_admin => query.filter(
|
||||||
|
Condition::any()
|
||||||
|
.add(samey_post::Column::IsPublic.into_simple_expr())
|
||||||
|
.add(samey_post::Column::UploaderId.eq(user.id)),
|
||||||
|
),
|
||||||
|
_ => query,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
91
src/views.rs
91
src/views.rs
|
|
@ -30,7 +30,7 @@ use crate::{
|
||||||
samey_post, samey_post_source, samey_tag, samey_tag_post,
|
samey_post, samey_post_source, samey_tag, samey_tag_post,
|
||||||
},
|
},
|
||||||
error::SameyError,
|
error::SameyError,
|
||||||
query::{SearchPost, get_tags_for_post, search_posts},
|
query::{PostOverview, filter_by_user, get_tags_for_post, search_posts},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_THUMBNAIL_DIMENSION: u32 = 192;
|
const MAX_THUMBNAIL_DIMENSION: u32 = 192;
|
||||||
|
|
@ -350,7 +350,7 @@ pub(crate) async fn select_tag(
|
||||||
struct PostsTemplate<'a> {
|
struct PostsTemplate<'a> {
|
||||||
tags: Option<Vec<&'a str>>,
|
tags: Option<Vec<&'a str>>,
|
||||||
tags_text: Option<String>,
|
tags_text: Option<String>,
|
||||||
posts: Vec<SearchPost>,
|
posts: Vec<PostOverview>,
|
||||||
page: u32,
|
page: u32,
|
||||||
page_count: u64,
|
page_count: u64,
|
||||||
}
|
}
|
||||||
|
|
@ -378,7 +378,7 @@ pub(crate) async fn posts_page(
|
||||||
.tags
|
.tags
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|tags| tags.split_whitespace().collect::<Vec<_>>());
|
.map(|tags| tags.split_whitespace().collect::<Vec<_>>());
|
||||||
let pagination = search_posts(tags.as_ref(), auth_session.user).paginate(&db, 50);
|
let pagination = search_posts(tags.as_ref(), auth_session.user.as_ref()).paginate(&db, 50);
|
||||||
let page_count = pagination.num_pages().await?;
|
let page_count = pagination.num_pages().await?;
|
||||||
let posts = pagination.fetch_page(page.saturating_sub(1) as u64).await?;
|
let posts = pagination.fetch_page(page.saturating_sub(1) as u64).await?;
|
||||||
let posts = posts
|
let posts = posts
|
||||||
|
|
@ -386,7 +386,7 @@ pub(crate) async fn posts_page(
|
||||||
.map(|post| {
|
.map(|post| {
|
||||||
let mut tags_vec: Vec<_> = post.tags.split_ascii_whitespace().collect();
|
let mut tags_vec: Vec<_> = post.tags.split_ascii_whitespace().collect();
|
||||||
tags_vec.sort();
|
tags_vec.sort();
|
||||||
SearchPost {
|
PostOverview {
|
||||||
tags: tags_vec.into_iter().join(" "),
|
tags: tags_vec.into_iter().join(" "),
|
||||||
..post
|
..post
|
||||||
}
|
}
|
||||||
|
|
@ -412,8 +412,11 @@ pub(crate) async fn posts_page(
|
||||||
struct ViewPostTemplate {
|
struct ViewPostTemplate {
|
||||||
post: samey_post::Model,
|
post: samey_post::Model,
|
||||||
tags: Vec<samey_tag::Model>,
|
tags: Vec<samey_tag::Model>,
|
||||||
|
tags_text: String,
|
||||||
sources: Vec<samey_post_source::Model>,
|
sources: Vec<samey_post_source::Model>,
|
||||||
can_edit: bool,
|
can_edit: bool,
|
||||||
|
parent_post: Option<PostOverview>,
|
||||||
|
children_posts: Vec<PostOverview>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn view_post(
|
pub(crate) async fn view_post(
|
||||||
|
|
@ -421,18 +424,64 @@ pub(crate) async fn view_post(
|
||||||
auth_session: AuthSession,
|
auth_session: AuthSession,
|
||||||
Path(post_id): Path<u32>,
|
Path(post_id): Path<u32>,
|
||||||
) -> Result<impl IntoResponse, SameyError> {
|
) -> Result<impl IntoResponse, SameyError> {
|
||||||
let tags = get_tags_for_post(post_id as i32).all(&db).await?;
|
let post_id = post_id as i32;
|
||||||
|
let tags = get_tags_for_post(post_id).all(&db).await?;
|
||||||
|
let tags_text = tags.iter().map(|tag| &tag.name).join(" ");
|
||||||
|
|
||||||
let sources = SameyPostSource::find()
|
let sources = SameyPostSource::find()
|
||||||
.filter(samey_post_source::Column::PostId.eq(post_id))
|
.filter(samey_post_source::Column::PostId.eq(post_id))
|
||||||
.all(&db)
|
.all(&db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let post = SameyPost::find_by_id(post_id as i32)
|
let post = SameyPost::find_by_id(post_id)
|
||||||
.one(&db)
|
.one(&db)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(SameyError::NotFound)?;
|
.ok_or(SameyError::NotFound)?;
|
||||||
|
|
||||||
|
let parent_post = if let Some(parent_id) = post.parent_id {
|
||||||
|
match filter_by_user(SameyPost::find_by_id(parent_id), auth_session.user.as_ref())
|
||||||
|
.one(&db)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Some(parent_post) => Some(PostOverview {
|
||||||
|
id: parent_id,
|
||||||
|
thumbnail: parent_post.thumbnail,
|
||||||
|
tags: get_tags_for_post(post_id)
|
||||||
|
.all(&db)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|tag| &tag.name)
|
||||||
|
.join(" "),
|
||||||
|
rating: parent_post.rating,
|
||||||
|
}),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let children_posts_models = filter_by_user(
|
||||||
|
SameyPost::find().filter(samey_post::Column::ParentId.eq(post_id)),
|
||||||
|
auth_session.user.as_ref(),
|
||||||
|
)
|
||||||
|
.all(&db)
|
||||||
|
.await?;
|
||||||
|
let mut children_posts = Vec::with_capacity(children_posts_models.capacity());
|
||||||
|
|
||||||
|
for child_post in children_posts_models.into_iter() {
|
||||||
|
children_posts.push(PostOverview {
|
||||||
|
id: child_post.id,
|
||||||
|
thumbnail: child_post.thumbnail,
|
||||||
|
tags: get_tags_for_post(child_post.id)
|
||||||
|
.all(&db)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|tag| &tag.name)
|
||||||
|
.join(" "),
|
||||||
|
rating: child_post.rating,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let can_edit = match auth_session.user {
|
let can_edit = match auth_session.user {
|
||||||
None => false,
|
None => false,
|
||||||
Some(user) => user.is_admin || post.uploader_id == user.id,
|
Some(user) => user.is_admin || post.uploader_id == user.id,
|
||||||
|
|
@ -446,8 +495,11 @@ pub(crate) async fn view_post(
|
||||||
ViewPostTemplate {
|
ViewPostTemplate {
|
||||||
post,
|
post,
|
||||||
tags,
|
tags,
|
||||||
|
tags_text,
|
||||||
sources,
|
sources,
|
||||||
can_edit,
|
can_edit,
|
||||||
|
parent_post,
|
||||||
|
children_posts,
|
||||||
}
|
}
|
||||||
.render()?,
|
.render()?,
|
||||||
))
|
))
|
||||||
|
|
@ -505,12 +557,14 @@ pub(crate) struct SubmitPostDetailsForm {
|
||||||
#[serde(rename = "source")]
|
#[serde(rename = "source")]
|
||||||
sources: Option<Vec<String>>,
|
sources: Option<Vec<String>>,
|
||||||
tags: String,
|
tags: String,
|
||||||
|
parent_post: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "submit_post_details.html")]
|
#[template(path = "submit_post_details.html")]
|
||||||
struct SubmitPostDetailsTemplate {
|
struct SubmitPostDetailsTemplate {
|
||||||
post: samey_post::Model,
|
post: samey_post::Model,
|
||||||
|
parent_post: Option<PostOverview>,
|
||||||
sources: Vec<samey_post_source::Model>,
|
sources: Vec<samey_post_source::Model>,
|
||||||
tags: Vec<samey_tag::Model>,
|
tags: Vec<samey_tag::Model>,
|
||||||
can_edit: bool,
|
can_edit: bool,
|
||||||
|
|
@ -529,7 +583,7 @@ pub(crate) async fn submit_post_details(
|
||||||
.await?
|
.await?
|
||||||
.ok_or(SameyError::NotFound)?;
|
.ok_or(SameyError::NotFound)?;
|
||||||
|
|
||||||
match auth_session.user {
|
match auth_session.user.as_ref() {
|
||||||
None => return Err(SameyError::Forbidden),
|
None => return Err(SameyError::Forbidden),
|
||||||
Some(user) => {
|
Some(user) => {
|
||||||
if !user.is_admin && post.uploader_id != user.id {
|
if !user.is_admin && post.uploader_id != user.id {
|
||||||
|
|
@ -546,6 +600,27 @@ pub(crate) async fn submit_post_details(
|
||||||
description if description.is_empty() => None,
|
description if description.is_empty() => None,
|
||||||
description => Some(description.to_owned()),
|
description => Some(description.to_owned()),
|
||||||
};
|
};
|
||||||
|
let parent_post = if let Some(parent_id) = body.parent_post.trim().parse().ok() {
|
||||||
|
match filter_by_user(SameyPost::find_by_id(parent_id), auth_session.user.as_ref())
|
||||||
|
.one(&db)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Some(parent_post) => Some(PostOverview {
|
||||||
|
id: parent_id,
|
||||||
|
thumbnail: parent_post.thumbnail,
|
||||||
|
tags: get_tags_for_post(post_id)
|
||||||
|
.all(&db)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|tag| &tag.name)
|
||||||
|
.join(" "),
|
||||||
|
rating: parent_post.rating,
|
||||||
|
}),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let is_public = body.is_public.is_some();
|
let is_public = body.is_public.is_some();
|
||||||
let post = SameyPost::update(samey_post::ActiveModel {
|
let post = SameyPost::update(samey_post::ActiveModel {
|
||||||
id: Set(post_id),
|
id: Set(post_id),
|
||||||
|
|
@ -553,6 +628,7 @@ pub(crate) async fn submit_post_details(
|
||||||
description: Set(description),
|
description: Set(description),
|
||||||
is_public: Set(is_public),
|
is_public: Set(is_public),
|
||||||
rating: Set(body.rating),
|
rating: Set(body.rating),
|
||||||
|
parent_id: Set(parent_post.as_ref().map(|post| post.id)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.exec(&db)
|
.exec(&db)
|
||||||
|
|
@ -622,6 +698,7 @@ pub(crate) async fn submit_post_details(
|
||||||
post,
|
post,
|
||||||
sources,
|
sources,
|
||||||
tags: upload_tags,
|
tags: upload_tags,
|
||||||
|
parent_post,
|
||||||
can_edit: true,
|
can_edit: true,
|
||||||
}
|
}
|
||||||
.render()?,
|
.render()?,
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@
|
||||||
</ul>
|
</ul>
|
||||||
<button hx-post="/post_source" hx-target="#sources" hx-swap="beforeend">+</button>
|
<button hx-post="/post_source" hx-target="#sources" hx-swap="beforeend">+</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Parent post</label>
|
||||||
|
<input name="parent_post" type="text" value="{% if let Some(parent_id) = post.parent_id %}{{ parent_id }}{% endif %}" />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button>Submit</button>
|
<button>Submit</button>
|
||||||
<button hx-get="/post_details/{{ post.id }}">Cancel</button>
|
<button hx-get="/post_details/{{ post.id }}">Cancel</button>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@
|
||||||
hx-trigger="input changed delay:400ms"
|
hx-trigger="input changed delay:400ms"
|
||||||
hx-target="next .tags-autocomplete"
|
hx-target="next .tags-autocomplete"
|
||||||
hx-on::after-settle="this.focus(); this.setSelectionRange(-1, -1);"
|
hx-on::after-settle="this.focus(); this.setSelectionRange(-1, -1);"
|
||||||
value=""
|
|
||||||
autofocus
|
autofocus
|
||||||
/>
|
/>
|
||||||
<ul class="tags-autocomplete" id="search-autocomplete"></ul>
|
<ul class="tags-autocomplete" id="search-autocomplete"></ul>
|
||||||
|
|
@ -40,7 +39,6 @@
|
||||||
hx-trigger="input changed delay:400ms"
|
hx-trigger="input changed delay:400ms"
|
||||||
hx-target="next .tags-autocomplete"
|
hx-target="next .tags-autocomplete"
|
||||||
hx-on::after-settle="this.focus(); this.setSelectionRange(-1, -1);"
|
hx-on::after-settle="this.focus(); this.setSelectionRange(-1, -1);"
|
||||||
value=""
|
|
||||||
/>
|
/>
|
||||||
<ul class="tags-autocomplete" id="upload-autocomplete"></ul>
|
<ul class="tags-autocomplete" id="upload-autocomplete"></ul>
|
||||||
<input
|
<input
|
||||||
|
|
@ -52,6 +50,13 @@
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
|
<article>
|
||||||
|
<h2>Create pool</h2>
|
||||||
|
<form method="post" action="/pool">
|
||||||
|
<input class="tags" type="text" id="pool-name" name="name" />
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
<article>
|
<article>
|
||||||
<a href="/logout">Log out</a>
|
<a href="/logout">Log out</a>
|
||||||
</article>
|
</article>
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
title="{{ post.tags }}"
|
title="{{ post.tags }}"
|
||||||
>
|
>
|
||||||
<img src="/files/{{ post.thumbnail }}" />
|
<img src="/files/{{ post.thumbnail }}" />
|
||||||
|
<div>{{ post.rating }}</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
{% include "post_details.html" %}
|
{% include "post_details.html" %} {% if let Some(parent_post) = parent_post %}
|
||||||
|
<article id="parent-post" hx-swap-oob="outerHTML">
|
||||||
|
<h2>Parent</h2>
|
||||||
|
<a href="/view/{{ parent_post.id }}" title="{{ parent_post.tags }}">
|
||||||
|
<img src="/files/{{ parent_post.thumbnail }}" />
|
||||||
|
<div>{{ parent_post.rating }}</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
{% else %}
|
||||||
|
<article id="parent-post" hx-swap-oob="outerHTML" hidden></article>
|
||||||
|
{% endif %}
|
||||||
<ul id="tags-list" hx-swap-oob="outerHTML">
|
<ul id="tags-list" hx-swap-oob="outerHTML">
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
<li>
|
<li>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<meta property="og:image" content="/files/{{ post.media }}" />
|
<meta property="og:image" content="/files/{{ post.media }}" />
|
||||||
<meta property="og:image:width" content="{{ post.width }}" />
|
<meta property="og:image:width" content="{{ post.width }}" />
|
||||||
<meta property="og:image:height" content="{{ post.height }}" />
|
<meta property="og:image:height" content="{{ post.height }}" />
|
||||||
<!-- <meta property="og:image:alt" content="TO-DO" /> -->
|
<meta property="og:image:alt" content="{{ tags_text }}" />
|
||||||
<meta
|
<meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="{% if let Some(description) = post.description %}{{ description }}{% endif %}"
|
content="{% if let Some(description) = post.description %}{{ description }}{% endif %}"
|
||||||
|
|
@ -34,6 +34,31 @@
|
||||||
<h2>Details</h2>
|
<h2>Details</h2>
|
||||||
{% include "post_details.html" %}
|
{% include "post_details.html" %}
|
||||||
</article>
|
</article>
|
||||||
|
{% if let Some(parent_post) = parent_post %}
|
||||||
|
<article id="parent-post">
|
||||||
|
<h2>Parent</h2>
|
||||||
|
<a href="/view/{{ parent_post.id }}" title="{{ parent_post.tags }}">
|
||||||
|
<img src="/files/{{ parent_post.thumbnail }}" />
|
||||||
|
<div>{{ parent_post.rating }}</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
{% else %}
|
||||||
|
<article id="parent-post" hidden></article>
|
||||||
|
{% endif %} {% if !children_posts.is_empty() %}
|
||||||
|
<article>
|
||||||
|
<h2>Child posts</h2>
|
||||||
|
<ul>
|
||||||
|
{% for child_post in children_posts %}
|
||||||
|
<li>
|
||||||
|
<a href="/view/{{ child_post.id }}" title="{{ child_post.tags }}">
|
||||||
|
<img src="/files/{{ child_post.thumbnail }}" />
|
||||||
|
<div>{{ child_post.rating }}</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
<article>
|
<article>
|
||||||
<h2>Tags</h2>
|
<h2>Tags</h2>
|
||||||
<ul id="tags-list">
|
<ul id="tags-list">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue