Functional post/file/tagging from database

This commit is contained in:
Evie Viau-Chow-Stuart 2023-03-24 21:14:33 -07:00
parent 13f6ef8443
commit 9c63feeeab
Signed by: evie
GPG key ID: 928652CDFCEC8099
35 changed files with 810 additions and 43 deletions

5
.gitignore vendored
View file

@ -1 +1,6 @@
/target
# Development
Caddyfile
certs/
images/

5
.woodpecker.yml Normal file
View file

@ -0,0 +1,5 @@
pipeline:
build:
image: rust:1.67-slim
commands:
- cargo build

35
Cargo.lock generated
View file

@ -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",
]

View file

@ -34,3 +34,4 @@ serde_derive = { version = "1.0.156" }
## Misc
tracing = "0.1.37"
tracing-subscriber = "0.3.16"
chrono = "0.4.24"

View file

@ -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),
]
}
}

View file

@ -13,7 +13,7 @@ impl MigrationTrait for Migration {
.if_not_exists()
.col(
ColumnDef::new(User::Id)
.text()
.big_integer()
.not_null()
.primary_key(),
)

View file

@ -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(

View file

@ -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
}

View 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,
}

View 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
}

View 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
}

View 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
View 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
View file

@ -0,0 +1,7 @@
use axum::response::IntoResponse;
pub async fn register(
) -> impl IntoResponse {
}

View file

@ -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
View 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 {}

View file

@ -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;

View file

@ -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
View 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 {}

View file

@ -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;

View file

@ -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
View 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 {}

View file

@ -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
View 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,
}
}

View file

@ -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
View 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
}
}
}

View file

@ -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
View 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
View file

@ -0,0 +1 @@
pico.css

20
static/css/main.css Normal file
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block title %}Posts{% endblock %}
{% block container %}container-fluid{% endblock %}