diff --git a/.gitignore b/.gitignore index ea8c4bf..33f12d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ /target + +# Development +Caddyfile +certs/ +images/ \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..6b55bbb --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,5 @@ +pipeline: + build: + image: rust:1.67-slim + commands: + - cargo build \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f7f74dc..6a6bd18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index c9390ec..0e3a1aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,4 @@ serde_derive = { version = "1.0.156" } ## Misc tracing = "0.1.37" tracing-subscriber = "0.3.16" +chrono = "0.4.24" \ No newline at end of file diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 307b297..8e439f2 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -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), ] } } diff --git a/migration/src/m20220101_000001_create_user.rs b/migration/src/m20220101_000001_create_user.rs index a50a8ac..5865ad4 100644 --- a/migration/src/m20220101_000001_create_user.rs +++ b/migration/src/m20220101_000001_create_user.rs @@ -13,7 +13,7 @@ impl MigrationTrait for Migration { .if_not_exists() .col( ColumnDef::new(User::Id) - .text() + .big_integer() .not_null() .primary_key(), ) diff --git a/migration/src/m20230322_103045_create_session.rs b/migration/src/m20230322_103045_create_session.rs index 963cdf8..f9b5eb4 100644 --- a/migration/src/m20230322_103045_create_session.rs +++ b/migration/src/m20230322_103045_create_session.rs @@ -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( diff --git a/migration/src/m20230322_110525_create_post.rs b/migration/src/m20230322_110525_create_post.rs index 821b64b..d467f19 100644 --- a/migration/src/m20230322_110525_create_post.rs +++ b/migration/src/m20230322_110525_create_post.rs @@ -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 } diff --git a/migration/src/m20230322_174812_create_tag.rs b/migration/src/m20230322_174812_create_tag.rs new file mode 100644 index 0000000..595ddee --- /dev/null +++ b/migration/src/m20230322_174812_create_tag.rs @@ -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, +} diff --git a/migration/src/m20230324_213941_create_post_tag.rs b/migration/src/m20230324_213941_create_post_tag.rs new file mode 100644 index 0000000..15eaae0 --- /dev/null +++ b/migration/src/m20230324_213941_create_post_tag.rs @@ -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 +} diff --git a/migration/src/m20230324_222553_create_file.rs b/migration/src/m20230324_222553_create_file.rs new file mode 100644 index 0000000..094208c --- /dev/null +++ b/migration/src/m20230324_222553_create_file.rs @@ -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 +} diff --git a/migration/src/m20230324_232343_add_file_to_post.rs b/migration/src/m20230324_232343_add_file_to_post.rs new file mode 100644 index 0000000..7788187 --- /dev/null +++ b/migration/src/m20230324_232343_add_file_to_post.rs @@ -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 + } +} diff --git a/src/api/auth/mod.rs b/src/api/auth/mod.rs new file mode 100644 index 0000000..66d6c08 --- /dev/null +++ b/src/api/auth/mod.rs @@ -0,0 +1,8 @@ +mod register; + +use axum::Router; +use crate::AppState; + +pub fn auth() -> Router { + Router::new() +} \ No newline at end of file diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs new file mode 100644 index 0000000..e74e2ec --- /dev/null +++ b/src/api/auth/register.rs @@ -0,0 +1,7 @@ +use axum::response::IntoResponse; + +pub async fn register( + +) -> impl IntoResponse { + +} \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs index ab385fc..69f794d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,6 +1,10 @@ +mod auth; + use axum::Router; +use crate::api::auth::auth; use crate::AppState; pub fn api() -> Router { Router::new() + .nest("/auth", auth()) } \ No newline at end of file diff --git a/src/entities/file.rs b/src/entities/file.rs new file mode 100644 index 0000000..b1312e7 --- /dev/null +++ b/src/entities/file.rs @@ -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 {} diff --git a/src/entities/mod.rs b/src/entities/mod.rs index c3159df..62c2034 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -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; diff --git a/src/entities/post.rs b/src/entities/post.rs index b18cad8..3a2458f 100644 --- a/src/entities/post.rs +++ b/src/entities/post.rs @@ -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, - 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 for Entity { + fn to() -> RelationDef { + Relation::PostTag.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } } impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/post_tag.rs b/src/entities/post_tag.rs new file mode 100644 index 0000000..273a340 --- /dev/null +++ b/src/entities/post_tag.rs @@ -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 for Entity { + fn to() -> RelationDef { + Relation::Post.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Tag.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/prelude.rs b/src/entities/prelude.rs index 7fe0f45..da93836 100644 --- a/src/entities/prelude.rs +++ b/src/entities/prelude.rs @@ -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; diff --git a/src/entities/session.rs b/src/entities/session.rs index 1813cf7..d93ce66 100644 --- a/src/entities/session.rs +++ b/src/entities/session.rs @@ -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, diff --git a/src/entities/tag.rs b/src/entities/tag.rs new file mode 100644 index 0000000..a8849d2 --- /dev/null +++ b/src/entities/tag.rs @@ -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 for Entity { + fn to() -> RelationDef { + Relation::PostTag.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/user.rs b/src/entities/user.rs index e171d9a..26bf9e4 100644 --- a/src/entities/user.rs +++ b/src/entities/user.rs @@ -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 for Entity { + fn to() -> RelationDef { + Relation::Post.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::Session.def() diff --git a/src/frontend/index.rs b/src/frontend/index.rs new file mode 100644 index 0000000..1fbebb6 --- /dev/null +++ b/src/frontend/index.rs @@ -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) -> 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, + } +} diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 6086a9a..5451d1d 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -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 { 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, } \ No newline at end of file diff --git a/src/frontend/post.rs b/src/frontend/post.rs new file mode 100644 index 0000000..87d0498 --- /dev/null +++ b/src/frontend/post.rs @@ -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, + description: String, + hash: String, + file_type: FileType, + resx: i64, + resy: i64, + file_size: i64, + file_id: i64, +} + +pub async fn post( + State(state): State, + Path(id): Path +) -> impl IntoResponse { + let options = Options::get(); + + let post: Option = 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::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 = PostTag::find() + .filter(post_tag::Column::PostId.eq(post.id)) + .all(&state.conn) + .await + .expect("Failed to access database?"); + + let mut tags: Vec = 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 for i32 { + fn from(code: FileType) -> i32 { + match code { + FileType::Image => 0, + FileType::Video => 1, + FileType::Unsupported => 9999999 + } + } +} + +impl From for FileType { + fn from(code: i32) -> FileType { + match code { + 0 => FileType::Image, + 1 => FileType::Video, + _ => FileType::Unsupported + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9059a52..d806b91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod options; mod api; mod frontend; mod entities; +mod rustflake; use std::net::SocketAddr; use axum::Router; diff --git a/src/rustflake.rs b/src/rustflake.rs new file mode 100644 index 0000000..522e9a1 --- /dev/null +++ b/src/rustflake.rs @@ -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>, +} + +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 + } +} \ No newline at end of file diff --git a/static/css/.gitignore b/static/css/.gitignore new file mode 100644 index 0000000..1246ab6 --- /dev/null +++ b/static/css/.gitignore @@ -0,0 +1 @@ +pico.css \ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css new file mode 100644 index 0000000..7f00a9c --- /dev/null +++ b/static/css/main.css @@ -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%; +} diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..ee87fe4 --- /dev/null +++ b/templates/404.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} + +{% block title %}404{% endblock %} + +{% block container %}container{% endblock %} + +{% block page %} +

404: Not Found

+

Sorry! What you were looking for can't be found here.

+ + Go Home +{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..4e7e2b9 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,29 @@ + + + + + + {% block title %}{{ title }}{% endblock %} - {{ site_name }} + + + + + + +
+ {% block page %}{% endblock %} +
+ + + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..b520b51 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block title %}Home{% endblock %} + +{% block container %}container{% endblock %} + +{% block page %} +
+

{{ site_name }}

+
+
+ + +
+
+ +
+

+ Currently hosting {{ posts_count }} + + {% if posts_count == 1 -%} + post. + {% else -%} + posts. + {% endif -%} +

+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/post.html b/templates/post.html new file mode 100644 index 0000000..6fe4191 --- /dev/null +++ b/templates/post.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} + +{% block title %}{{ post_id }}{% endblock %} + +{% block container %}container-fluid{% endblock %} + +{% block page %} +
+
+ Tags +
    + {% for tag in tags %} +
  • {{ tag }}
  • + {% endfor %} +
+ + Information +
    +
  • Score: N/A
  • +
  • Favourites: N/A
  • +
  • ID: {{ post_id }}
  • +
  • Posted: N/A
  • +
  • Source: N/A
  • +
  • Resolution: {{ resx }}x{{ resy }}
  • +
  • File Size: {{ file_size }} bytes
  • +
  • Hash: {{ hash }}
  • +
+ + Search + +
+ +
+ {% match file_type %} + {% when FileType::Image %} + + {% when FileType::Video %} + {% when FileType::Unsupported %} +

UNSUPPORTED FILE

+ {% endmatch %} +
+ {{ description }} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/posts.html b/templates/posts.html new file mode 100644 index 0000000..1243318 --- /dev/null +++ b/templates/posts.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block title %}Posts{% endblock %} + +{% block container %}container-fluid{% endblock %} \ No newline at end of file