Functional post/file/tagging from database
This commit is contained in:
parent
13f6ef8443
commit
9c63feeeab
35 changed files with 810 additions and 43 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1 +1,6 @@
|
|||
/target
|
||||
|
||||
# Development
|
||||
Caddyfile
|
||||
certs/
|
||||
images/
|
5
.woodpecker.yml
Normal file
5
.woodpecker.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
pipeline:
|
||||
build:
|
||||
image: rust:1.67-slim
|
||||
commands:
|
||||
- cargo build
|
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -564,9 +564,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -646,6 +649,7 @@ dependencies = [
|
|||
"askama_axum",
|
||||
"axum",
|
||||
"blake3",
|
||||
"chrono",
|
||||
"migration",
|
||||
"rand",
|
||||
"sea-orm",
|
||||
|
@ -1008,7 +1012,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1374,7 +1378,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
|
@ -1907,7 +1911,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
|
@ -1970,7 +1974,7 @@ dependencies = [
|
|||
"rust_decimal",
|
||||
"sea-query-derive",
|
||||
"serde_json",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -1986,7 +1990,7 @@ dependencies = [
|
|||
"sea-query",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -2254,7 +2258,7 @@ dependencies = [
|
|||
"sqlx-rt",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
"tokio-stream",
|
||||
"url",
|
||||
"uuid",
|
||||
|
@ -2387,6 +2391,17 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.20"
|
||||
|
@ -2743,6 +2758,12 @@ dependencies = [
|
|||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -2988,5 +3009,5 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"quick-error",
|
||||
"regex",
|
||||
"time",
|
||||
"time 0.3.20",
|
||||
]
|
||||
|
|
|
@ -34,3 +34,4 @@ serde_derive = { version = "1.0.156" }
|
|||
## Misc
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = "0.3.16"
|
||||
chrono = "0.4.24"
|
|
@ -3,6 +3,12 @@ pub use sea_orm_migration::prelude::*;
|
|||
mod m20220101_000001_create_user;
|
||||
mod m20230322_103045_create_session;
|
||||
mod m20230322_110525_create_post;
|
||||
mod m20230322_174812_create_tag;
|
||||
mod m20230324_213941_create_post_tag;
|
||||
mod m20230324_222553_create_file;
|
||||
mod m20230324_232343_add_file_to_post;
|
||||
|
||||
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
|
@ -13,6 +19,10 @@ impl MigratorTrait for Migrator {
|
|||
Box::new(m20220101_000001_create_user::Migration),
|
||||
Box::new(m20230322_103045_create_session::Migration),
|
||||
Box::new(m20230322_110525_create_post::Migration),
|
||||
Box::new(m20230322_174812_create_tag::Migration),
|
||||
Box::new(m20230324_213941_create_post_tag::Migration),
|
||||
Box::new(m20230324_222553_create_file::Migration),
|
||||
Box::new(m20230324_232343_add_file_to_post::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ impl MigrationTrait for Migration {
|
|||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(User::Id)
|
||||
.text()
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
|
|
|
@ -14,11 +14,11 @@ impl MigrationTrait for Migration {
|
|||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Session::Id)
|
||||
.text()
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Session::User).text().not_null())
|
||||
.col(ColumnDef::new(Session::User).big_integer().not_null())
|
||||
.col(ColumnDef::new(Session::Token).text().not_null())
|
||||
.col(ColumnDef::new(Session::Expiry).date_time().not_null())
|
||||
.foreign_key(
|
||||
|
|
|
@ -14,26 +14,18 @@ impl MigrationTrait for Migration {
|
|||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Post::Id)
|
||||
.text()
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Post::Description).text().not_null().default(""))
|
||||
.col(ColumnDef::new(Post::Uploader).text().not_null())
|
||||
.col(ColumnDef::new(Post::Approver).text())
|
||||
.col(ColumnDef::new(Post::Rating).integer().not_null())
|
||||
.col(ColumnDef::new(Post::Uploader).big_integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-post_uploader-user_id")
|
||||
.from(Post::Table, Post::Uploader)
|
||||
.to(User::Table, User::Id)
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-post_approver-user_id")
|
||||
.from(Post::Table, Post::Approver)
|
||||
.to(User::Table, User::Id)
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
|
@ -48,11 +40,9 @@ impl MigrationTrait for Migration {
|
|||
|
||||
/// Learn more at https://docs.rs/sea-query#iden
|
||||
#[derive(Iden)]
|
||||
enum Post {
|
||||
pub enum Post {
|
||||
Table,
|
||||
Id,
|
||||
Description,
|
||||
Uploader,
|
||||
Approver,
|
||||
Rating
|
||||
}
|
||||
|
|
39
migration/src/m20230322_174812_create_tag.rs
Normal file
39
migration/src/m20230322_174812_create_tag.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Tag::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Tag::Id)
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Tag::Name).text().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Tag::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn more at https://docs.rs/sea-query#iden
|
||||
#[derive(Iden)]
|
||||
pub enum Tag {
|
||||
Table,
|
||||
Id,
|
||||
Name,
|
||||
}
|
55
migration/src/m20230324_213941_create_post_tag.rs
Normal file
55
migration/src/m20230324_213941_create_post_tag.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
use crate::m20230322_110525_create_post::Post;
|
||||
use crate::m20230322_174812_create_tag::Tag;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(PostTag::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(PostTag::Id)
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(PostTag::TagId).big_integer().not_null())
|
||||
.col(ColumnDef::new(PostTag::PostId).big_integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-posttag-post_id")
|
||||
.from(PostTag::Table, PostTag::PostId)
|
||||
.to(Post::Table, Post::Id)
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk-posttag-tag_id")
|
||||
.from(PostTag::Table, PostTag::TagId)
|
||||
.to(Tag::Table, Tag::Id)
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(PostTag::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn more at https://docs.rs/sea-query#iden
|
||||
#[derive(Iden)]
|
||||
enum PostTag {
|
||||
Table,
|
||||
Id,
|
||||
PostId,
|
||||
TagId
|
||||
}
|
47
migration/src/m20230324_222553_create_file.rs
Normal file
47
migration/src/m20230324_222553_create_file.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(File::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(File::Id)
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(File::Hash).text().not_null())
|
||||
.col(ColumnDef::new(File::Size).big_integer().not_null())
|
||||
.col(ColumnDef::new(File::Type).integer().not_null())
|
||||
.col(ColumnDef::new(File::ResX).big_integer().not_null())
|
||||
.col(ColumnDef::new(File::ResY).big_integer().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(File::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn more at https://docs.rs/sea-query#iden
|
||||
#[derive(Iden)]
|
||||
pub enum File {
|
||||
Table,
|
||||
Id,
|
||||
Hash,
|
||||
Size,
|
||||
Type,
|
||||
ResX,
|
||||
ResY
|
||||
}
|
63
migration/src/m20230324_232343_add_file_to_post.rs
Normal file
63
migration/src/m20230324_232343_add_file_to_post.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
use crate::m20230322_110525_create_post::Post;
|
||||
use crate::m20230324_222553_create_file::File;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Post::Table)
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("file"))
|
||||
.big_integer()
|
||||
.not_null()
|
||||
)
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("thumbnail"))
|
||||
.big_integer()
|
||||
.not_null()
|
||||
)
|
||||
.add_foreign_key(
|
||||
TableForeignKey::new()
|
||||
.name("fk-post_file-file_id")
|
||||
.from_tbl(Post::Table)
|
||||
.from_col(Alias::new("file"))
|
||||
.to_tbl(File::Table)
|
||||
.to_col(File::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
)
|
||||
.add_foreign_key(
|
||||
TableForeignKey::new()
|
||||
.name("fk-post_file-thumbnail_id")
|
||||
.from_tbl(Post::Table)
|
||||
.from_col(Alias::new("thumbnail"))
|
||||
.to_tbl(File::Table)
|
||||
.to_col(File::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
)
|
||||
.to_owned()
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Post::Table)
|
||||
.drop_foreign_key(Alias::new("fk-post_file-file_id"))
|
||||
.drop_foreign_key(Alias::new("fk-post_file-thumbnail_id"))
|
||||
.drop_column(Alias::new("file"))
|
||||
.drop_column(Alias::new("thumbnail"))
|
||||
.to_owned()
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
8
src/api/auth/mod.rs
Normal file
8
src/api/auth/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
mod register;
|
||||
|
||||
use axum::Router;
|
||||
use crate::AppState;
|
||||
|
||||
pub fn auth() -> Router<AppState> {
|
||||
Router::new()
|
||||
}
|
7
src/api/auth/register.rs
Normal file
7
src/api/auth/register.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
use axum::response::IntoResponse;
|
||||
|
||||
pub async fn register(
|
||||
|
||||
) -> impl IntoResponse {
|
||||
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
mod auth;
|
||||
|
||||
use axum::Router;
|
||||
use crate::api::auth::auth;
|
||||
use crate::AppState;
|
||||
|
||||
pub fn api() -> Router<AppState> {
|
||||
Router::new()
|
||||
.nest("/auth", auth())
|
||||
}
|
21
src/entities/file.rs
Normal file
21
src/entities/file.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "file")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: i64,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub hash: String,
|
||||
pub size: i64,
|
||||
pub r#type: i32,
|
||||
pub res_x: i64,
|
||||
pub res_y: i64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
pub mod prelude;
|
||||
|
||||
pub mod file;
|
||||
pub mod post;
|
||||
pub mod post_tag;
|
||||
pub mod session;
|
||||
pub mod tag;
|
||||
pub mod user;
|
||||
|
|
|
@ -5,27 +5,35 @@ use sea_orm::entity::prelude::*;
|
|||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "post")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false, column_type = "Text")]
|
||||
pub id: String,
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: i64,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub description: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub uploader: String,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub approver: Option<String>,
|
||||
pub rating: i32,
|
||||
pub uploader: i64,
|
||||
pub file: i64,
|
||||
pub thumbnail: i64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::Approver",
|
||||
to = "super::user::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "NoAction"
|
||||
belongs_to = "super::file::Entity",
|
||||
from = "Column::File",
|
||||
to = "super::file::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
User2,
|
||||
File2,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::file::Entity",
|
||||
from = "Column::Thumbnail",
|
||||
to = "super::file::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
File1,
|
||||
#[sea_orm(has_many = "super::post_tag::Entity")]
|
||||
PostTag,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::Uploader",
|
||||
|
@ -33,7 +41,19 @@ pub enum Relation {
|
|||
on_update = "NoAction",
|
||||
on_delete = "NoAction"
|
||||
)]
|
||||
User1,
|
||||
User,
|
||||
}
|
||||
|
||||
impl Related<super::post_tag::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::PostTag.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::User.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
46
src/entities/post_tag.rs
Normal file
46
src/entities/post_tag.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "post_tag")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: i64,
|
||||
pub tag_id: i64,
|
||||
pub post_id: i64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::post::Entity",
|
||||
from = "Column::PostId",
|
||||
to = "super::post::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "NoAction"
|
||||
)]
|
||||
Post,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::tag::Entity",
|
||||
from = "Column::TagId",
|
||||
to = "super::tag::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "NoAction"
|
||||
)]
|
||||
Tag,
|
||||
}
|
||||
|
||||
impl Related<super::post::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Post.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::tag::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Tag.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -1,5 +1,8 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1
|
||||
|
||||
pub use super::file::Entity as File;
|
||||
pub use super::post::Entity as Post;
|
||||
pub use super::post_tag::Entity as PostTag;
|
||||
pub use super::session::Entity as Session;
|
||||
pub use super::tag::Entity as Tag;
|
||||
pub use super::user::Entity as User;
|
||||
|
|
|
@ -5,10 +5,9 @@ use sea_orm::entity::prelude::*;
|
|||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "session")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false, column_type = "Text")]
|
||||
pub id: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub user: String,
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: i64,
|
||||
pub user: i64,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub token: String,
|
||||
pub expiry: DateTime,
|
||||
|
|
26
src/entities/tag.rs
Normal file
26
src/entities/tag.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.1
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "tag")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: i64,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::post_tag::Entity")]
|
||||
PostTag,
|
||||
}
|
||||
|
||||
impl Related<super::post_tag::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::PostTag.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -5,8 +5,8 @@ use sea_orm::entity::prelude::*;
|
|||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "user")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false, column_type = "Text")]
|
||||
pub id: String,
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: i64,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub username: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
|
@ -19,10 +19,18 @@ pub struct Model {
|
|||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::post::Entity")]
|
||||
Post,
|
||||
#[sea_orm(has_many = "super::session::Entity")]
|
||||
Session,
|
||||
}
|
||||
|
||||
impl Related<super::post::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Post.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::session::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Session.def()
|
||||
|
|
31
src/frontend/index.rs
Normal file
31
src/frontend/index.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use askama::Template;
|
||||
use axum::extract::State;
|
||||
use sea_orm::{EntityTrait, PaginatorTrait};
|
||||
use crate::AppState;
|
||||
use crate::options::{Opt, Options};
|
||||
use crate::entities::{prelude::*, *};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
pub struct IndexTemplate {
|
||||
site_name: String,
|
||||
url: String,
|
||||
asset_endpoint: String,
|
||||
posts_count: u64
|
||||
}
|
||||
|
||||
pub async fn index(State(state): State<AppState>) -> IndexTemplate {
|
||||
let options = Options::get();
|
||||
|
||||
let posts_count: u64 = post::Entity::find()
|
||||
.count(&state.conn)
|
||||
.await
|
||||
.expect("Failed to get count of posts!");
|
||||
|
||||
IndexTemplate {
|
||||
site_name: options.name,
|
||||
url: options.url,
|
||||
asset_endpoint: options.asset_endpoint,
|
||||
posts_count,
|
||||
}
|
||||
}
|
|
@ -1,6 +1,22 @@
|
|||
mod index;
|
||||
mod post;
|
||||
|
||||
use axum::Router;
|
||||
use axum::routing::get;
|
||||
use askama::Template;
|
||||
use crate::AppState;
|
||||
|
||||
|
||||
pub fn frontend() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(index::index))
|
||||
.route("/post/:id", get(post::post))
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "404.html")]
|
||||
struct NotFoundTemplate {
|
||||
site_name: String,
|
||||
url: String,
|
||||
asset_endpoint: String,
|
||||
}
|
125
src/frontend/post.rs
Normal file
125
src/frontend/post.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use axum::extract::{Path, State};
|
||||
use crate::AppState;
|
||||
use askama::Template;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use crate::options::{Opt, Options};
|
||||
|
||||
use sea_orm::*;
|
||||
use tracing::log::error;
|
||||
use crate::entities::{post, post_tag, tag, file};
|
||||
use crate::entities::prelude::{File, Post, PostTag, Tag};
|
||||
use crate::frontend::NotFoundTemplate;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "post.html")]
|
||||
pub struct PostTemplate {
|
||||
site_name: String,
|
||||
url: String,
|
||||
asset_endpoint: String,
|
||||
image_endpoint: String,
|
||||
post_id: i64,
|
||||
tags: Vec<String>,
|
||||
description: String,
|
||||
hash: String,
|
||||
file_type: FileType,
|
||||
resx: i64,
|
||||
resy: i64,
|
||||
file_size: i64,
|
||||
file_id: i64,
|
||||
}
|
||||
|
||||
pub async fn post(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<i64>
|
||||
) -> impl IntoResponse {
|
||||
let options = Options::get();
|
||||
|
||||
let post: Option<post::Model> = Post::find_by_id(id)
|
||||
.one(&state.conn)
|
||||
.await
|
||||
.expect("Failed to access database?");
|
||||
|
||||
if post.is_none() {
|
||||
return (StatusCode::NOT_FOUND, NotFoundTemplate {
|
||||
site_name: options.name,
|
||||
url: options.url,
|
||||
asset_endpoint: options.asset_endpoint,
|
||||
}).into_response();
|
||||
}
|
||||
|
||||
let post = post.unwrap();
|
||||
|
||||
let file: Option<file::Model> = File::find_by_id(post.file)
|
||||
.one(&state.conn)
|
||||
.await
|
||||
.expect("Failed to access database?");
|
||||
|
||||
if file.is_none() {
|
||||
error!("Post {} exists but no files for it exist!", post.id);
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, ()).into_response();
|
||||
}
|
||||
|
||||
let file = file.unwrap();
|
||||
|
||||
let tags_raw: Vec<post_tag::Model> = PostTag::find()
|
||||
.filter(post_tag::Column::PostId.eq(post.id))
|
||||
.all(&state.conn)
|
||||
.await
|
||||
.expect("Failed to access database?");
|
||||
|
||||
let mut tags: Vec<String> = vec![];
|
||||
for i in tags_raw {
|
||||
let tag = Tag::find_by_id(i.tag_id)
|
||||
.one(&state.conn)
|
||||
.await
|
||||
.expect("Failed to access database?");
|
||||
|
||||
if tag.is_some() {
|
||||
tags.push(tag.unwrap().name);
|
||||
}
|
||||
}
|
||||
|
||||
(StatusCode::OK, PostTemplate {
|
||||
site_name: options.name,
|
||||
url: options.url,
|
||||
asset_endpoint: options.asset_endpoint,
|
||||
image_endpoint: options.image_endpoint,
|
||||
post_id: post.id,
|
||||
tags,
|
||||
description: post.description,
|
||||
hash: file.hash,
|
||||
file_type: file.r#type.into(),
|
||||
resx: file.res_x,
|
||||
resy: file.res_y,
|
||||
file_size: file.size,
|
||||
file_id: file.id,
|
||||
}).into_response()
|
||||
}
|
||||
|
||||
// TODO: Move this to uploading module when that starts being worked on
|
||||
pub enum FileType {
|
||||
Image,
|
||||
Video,
|
||||
Unsupported
|
||||
}
|
||||
|
||||
impl From<FileType> for i32 {
|
||||
fn from(code: FileType) -> i32 {
|
||||
match code {
|
||||
FileType::Image => 0,
|
||||
FileType::Video => 1,
|
||||
FileType::Unsupported => 9999999
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for FileType {
|
||||
fn from(code: i32) -> FileType {
|
||||
match code {
|
||||
0 => FileType::Image,
|
||||
1 => FileType::Video,
|
||||
_ => FileType::Unsupported
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ mod options;
|
|||
mod api;
|
||||
mod frontend;
|
||||
mod entities;
|
||||
mod rustflake;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use axum::Router;
|
||||
|
|
69
src/rustflake.rs
Normal file
69
src/rustflake.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use chrono::Utc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct Snowflake {
|
||||
epoch: i64,
|
||||
worker_id: i64,
|
||||
datacenter_id: i64,
|
||||
sequence: i64,
|
||||
time: Arc<Mutex<i64>>,
|
||||
}
|
||||
|
||||
impl Snowflake {
|
||||
/// Default configuration for a Snowflake
|
||||
pub fn default() -> Snowflake {
|
||||
Snowflake {
|
||||
epoch: 1672531200,
|
||||
worker_id: 1,
|
||||
datacenter_id: 1,
|
||||
sequence: 0,
|
||||
time: Arc::new(Mutex::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new Snowflake with the provided configuration
|
||||
pub fn new(epoch: i64, worker_id: i64, datacenter_id: i64) -> Snowflake {
|
||||
Snowflake {
|
||||
epoch,
|
||||
worker_id,
|
||||
datacenter_id,
|
||||
sequence: 0,
|
||||
time: Arc::new(Mutex::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn epoch(&mut self, epoch: i64) -> &mut Self {
|
||||
self.epoch = epoch;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn worker_id(&mut self, worker_id: i64) -> &mut Self {
|
||||
self.worker_id = worker_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn datacenter_id(&mut self, datacenter_id: i64) -> &mut Self {
|
||||
self.datacenter_id = datacenter_id;
|
||||
self
|
||||
}
|
||||
|
||||
/// Generate a new Snowflake
|
||||
pub fn generate(&mut self) -> i64 {
|
||||
let mut last_timestamp = self.time.lock().expect("Snowflake MutexGuard panic!");
|
||||
let mut timestamp = self.get_time();
|
||||
if timestamp == *last_timestamp {
|
||||
self.sequence = (self.sequence + 1) & (-1 ^ (-1 << 12));
|
||||
if self.sequence == 0 && timestamp <= *last_timestamp {
|
||||
timestamp = self.get_time();
|
||||
}
|
||||
} else {
|
||||
self.sequence = 0;
|
||||
}
|
||||
*last_timestamp = timestamp;
|
||||
(timestamp << 22) | (self.worker_id << 17) | (self.datacenter_id << 12) | self.sequence
|
||||
}
|
||||
|
||||
fn get_time(&self) -> i64 {
|
||||
Utc::now().timestamp_millis() - self.epoch
|
||||
}
|
||||
}
|
1
static/css/.gitignore
vendored
Normal file
1
static/css/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
pico.css
|
20
static/css/main.css
Normal file
20
static/css/main.css
Normal file
|
@ -0,0 +1,20 @@
|
|||
div.index {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Main */
|
||||
@media (min-width: 992px) {
|
||||
.post {
|
||||
grid-template-columns: 25% auto;
|
||||
}
|
||||
}
|
||||
|
||||
div.post {
|
||||
padding-left: 5rem;
|
||||
padding-right: 5rem;
|
||||
}
|
||||
|
||||
.file {
|
||||
max-height: 95vh;
|
||||
max-width: 100%;
|
||||
}
|
12
templates/404.html
Normal file
12
templates/404.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}404{% endblock %}
|
||||
|
||||
{% block container %}container{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<h1>404: Not Found</h1>
|
||||
<p>Sorry! What you were looking for can't be found here.</p>
|
||||
|
||||
<a role="button" href="{{ url }}">Go Home</a>
|
||||
{% endblock %}
|
29
templates/base.html
Normal file
29
templates/base.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}{{ title }}{% endblock %} - {{ site_name }}</title>
|
||||
<link rel="stylesheet" href="{{ asset_endpoint }}/css/pico.css" />
|
||||
<link rel="stylesheet" href="{{ asset_endpoint }}/css/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<nav class="container-fluid">
|
||||
<ul>
|
||||
<li><a href="{{ url }}">Home</a></li>
|
||||
<li><a href="{{ url }}/posts">Posts</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="{{ url }}/account">Account</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main class="{% block container %}{% endblock %}">
|
||||
{% block page %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="container">
|
||||
<i>Powered by <a href="https://git.gaycatgirl.sex/evie/corrugatedcardboard">CorrugatedCardboard</a></i>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
29
templates/index.html
Normal file
29
templates/index.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Home{% endblock %}
|
||||
|
||||
{% block container %}container{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<div class="index">
|
||||
<h1>{{ site_name }}</h1>
|
||||
<section>
|
||||
<form action="{{ url }}/posts" method="get">
|
||||
<input type="text" name="search" />
|
||||
<button>Search</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>
|
||||
Currently hosting <b>{{ posts_count }}</b>
|
||||
|
||||
{% if posts_count == 1 -%}
|
||||
post.
|
||||
{% else -%}
|
||||
posts.
|
||||
{% endif -%}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
48
templates/post.html
Normal file
48
templates/post.html
Normal file
|
@ -0,0 +1,48 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ post_id }}{% endblock %}
|
||||
|
||||
{% block container %}container-fluid{% endblock %}
|
||||
|
||||
{% block page %}
|
||||
<div class="grid post">
|
||||
<div class="side">
|
||||
<b>Tags</b>
|
||||
<ul>
|
||||
{% for tag in tags %}
|
||||
<li><a href="{{ url }}/posts?search={{ tag }}">{{ tag }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<b>Information</b>
|
||||
<ul>
|
||||
<li>Score: N/A</li>
|
||||
<li>Favourites: N/A</li>
|
||||
<li>ID: {{ post_id }}</li>
|
||||
<li>Posted: N/A</li>
|
||||
<li>Source: N/A</li>
|
||||
<li>Resolution: {{ resx }}x{{ resy }}</li>
|
||||
<li>File Size: {{ file_size }} bytes</li>
|
||||
<li>Hash: {{ hash }}</li>
|
||||
</ul>
|
||||
|
||||
<b>Search</b>
|
||||
<uL>
|
||||
<li><a href="https://inkbunny.net/submissionsviewall.php?&mode=search&text={{ hash }}&md5=yes">Inkbunny MD5 Search</a></li>
|
||||
</uL>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
{% match file_type %}
|
||||
{% when FileType::Image %}
|
||||
<img src="{{ image_endpoint }}/{{ file_id }}" class="file" />
|
||||
{% when FileType::Video %}
|
||||
{% when FileType::Unsupported %}
|
||||
<h1>UNSUPPORTED FILE</h1>
|
||||
{% endmatch %}
|
||||
<blockquote>
|
||||
{{ description }}
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
5
templates/posts.html
Normal file
5
templates/posts.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Posts{% endblock %}
|
||||
|
||||
{% block container %}container-fluid{% endblock %}
|
Loading…
Add table
Reference in a new issue