Merge pull request 'User Authentication and Other fixes' (#1) from user-auth into main

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2023-02-05 09:01:07 +00:00
62 changed files with 23660 additions and 1394 deletions

6
.env-dev Normal file
View File

@@ -0,0 +1,6 @@
DATABASE_URL="sqlite:../data-debug/db/sqlite.db?mode=rwc"
IMAGES_DIR="../data-debug/images"
CORS_URL="*"
MEILI_URL="http://localhost:7700"
MEILI_KEY="asdasdasd"
BACKEND_URL="http://localhost:8081"

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@ images/*
data/* data/*
data-debug/* data-debug/*
booksman.tar booksman.tar
frontend/node_modules/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
# Cargo.lock # Cargo.lock

637
backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ booksman-orm = { path = "../orm" }
booksman-search = { path = "../search" } booksman-search = { path = "../search" }
migration = { path = "../migration" } migration = { path = "../migration" }
entity = { path = "../entity" } entity = { path = "../entity" }
axum = "^0.5" axum = "^0.6"
axum-extra = { version = "^0.3", features = ["spa"] } axum-extra = { version = "^0.3", features = ["spa"] }
clap = { version = "^3", features = ["derive"] } clap = { version = "^3", features = ["derive"] }
dotenvy = "0.15.0" dotenvy = "0.15.0"
@@ -27,9 +27,16 @@ tracing = "^0.1"
tracing-subscriber = "^0.3" tracing-subscriber = "^0.3"
itertools = "0.10" itertools = "0.10"
chrono = "0.4" chrono = "0.4"
axum-login = "0.4"
axum-macros = "0.3"
#features = ["sqlite"]
[dependencies.rand]
version = "0.8.5"
features = ["min_const_gen"]
[dependencies.sea-orm] [dependencies.sea-orm]
version = "^0.9.2" # sea-orm version version = "^0.10.6" # sea-orm version
features = [ features = [
"debug-print", "debug-print",
"runtime-tokio-native-tls", "runtime-tokio-native-tls",

File diff suppressed because it is too large Load Diff

View File

@@ -13,4 +13,4 @@ serde = { version = "1", features = ["derive"] }
chrono = "0.4" chrono = "0.4"
[dependencies.sea-orm] [dependencies.sea-orm]
version = "^0.9.2" # sea-orm version version = "^0.10.6" # sea-orm version

View File

@@ -1,8 +1,8 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "book")] #[sea_orm(table_name = "book")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
@@ -19,12 +19,15 @@ pub struct Model {
pub time_added: Option<String>, pub time_added: Option<String>,
pub rating: Option<i32>, pub rating: Option<i32>,
pub comments: Option<String>, pub comments: Option<String>,
pub user_id: i32,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation { pub enum Relation {
#[sea_orm(has_many = "super::book_author::Entity")] #[sea_orm(has_many = "super::book_author::Entity")]
BookAuthor, BookAuthor,
#[sea_orm(has_many = "super::book_isbn::Entity")]
BookIsbn,
#[sea_orm(has_many = "super::book_person::Entity")] #[sea_orm(has_many = "super::book_person::Entity")]
BookPerson, BookPerson,
#[sea_orm(has_many = "super::book_place::Entity")] #[sea_orm(has_many = "super::book_place::Entity")]
@@ -33,8 +36,14 @@ pub enum Relation {
BookSubject, BookSubject,
#[sea_orm(has_many = "super::book_time::Entity")] #[sea_orm(has_many = "super::book_time::Entity")]
BookTime, BookTime,
#[sea_orm(has_many = "super::book_isbn::Entity")] #[sea_orm(
BookIsbn, belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
User,
} }
impl Related<super::book_author::Entity> for Entity { impl Related<super::book_author::Entity> for Entity {
@@ -43,6 +52,12 @@ impl Related<super::book_author::Entity> for Entity {
} }
} }
impl Related<super::book_isbn::Entity> for Entity {
fn to() -> RelationDef {
Relation::BookIsbn.def()
}
}
impl Related<super::book_person::Entity> for Entity { impl Related<super::book_person::Entity> for Entity {
fn to() -> RelationDef { fn to() -> RelationDef {
Relation::BookPerson.def() Relation::BookPerson.def()
@@ -67,9 +82,9 @@ impl Related<super::book_time::Entity> for Entity {
} }
} }
impl Related<super::book_isbn::Entity> for Entity { impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef { fn to() -> RelationDef {
Relation::BookIsbn.def() Relation::User.def()
} }
} }

View File

@@ -1,8 +1,8 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "book_author")] #[sea_orm(table_name = "book_author")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]

View File

@@ -1,8 +1,8 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "book_isbn")] #[sea_orm(table_name = "book_isbn")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]

View File

@@ -1,8 +1,8 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "book_person")] #[sea_orm(table_name = "book_person")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]

View File

@@ -1,8 +1,8 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "book_place")] #[sea_orm(table_name = "book_place")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]

View File

@@ -1,8 +1,8 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "book_subject")] #[sea_orm(table_name = "book_subject")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]

View File

@@ -1,8 +1,8 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "book_time")] #[sea_orm(table_name = "book_time")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]

View File

@@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
pub mod prelude; pub mod prelude;
@@ -9,3 +9,4 @@ pub mod book_person;
pub mod book_place; pub mod book_place;
pub mod book_subject; pub mod book_subject;
pub mod book_time; pub mod book_time;
pub mod user;

View File

@@ -1,4 +1,4 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
pub use super::book::Entity as Book; pub use super::book::Entity as Book;
pub use super::book_author::Entity as BookAuthor; pub use super::book_author::Entity as BookAuthor;
@@ -7,3 +7,4 @@ pub use super::book_person::Entity as BookPerson;
pub use super::book_place::Entity as BookPlace; pub use super::book_place::Entity as BookPlace;
pub use super::book_subject::Entity as BookSubject; pub use super::book_subject::Entity as BookSubject;
pub use super::book_time::Entity as BookTime; pub use super::book_time::Entity as BookTime;
pub use super::user::Entity as User;

View File

@@ -0,0 +1,26 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "user")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub user_name: Option<String>,
pub password_hash: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::book::Entity")]
Book,
}
impl Related<super::book::Entity> for Entity {
fn to() -> RelationDef {
Relation::Book.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -1,3 +1 @@
pub mod entities; pub mod entities;

View File

@@ -13,7 +13,7 @@ async-std = { version = "^1", features = ["attributes", "tokio1"] }
chrono = "0.4" chrono = "0.4"
[dependencies.sea-orm-migration] [dependencies.sea-orm-migration]
version = "^0.9.2" version = "^0.10.6"
features = [ features = [
# Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
# View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.

View File

@@ -10,6 +10,30 @@ impl MigrationTrait for Migration {
// Replace the sample below with your own migration scripts // Replace the sample below with your own migration scripts
//todo!(); //todo!();
manager
.create_table(
Table::create()
.table(User::Table)
.if_not_exists()
.col(
ColumnDef::new(User::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(User::UserName)
.string()
.unique_key()
.not_null(),
)
.col(ColumnDef::new(User::PasswordHash).string().not_null())
.to_owned(),
)
.await
.expect("Migration failed");
manager manager
.create_table( .create_table(
Table::create() Table::create()
@@ -34,9 +58,19 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Book::TimeAdded).time()) .col(ColumnDef::new(Book::TimeAdded).time())
.col(ColumnDef::new(Book::Rating).integer()) .col(ColumnDef::new(Book::Rating).integer())
.col(ColumnDef::new(Book::Comments).string()) .col(ColumnDef::new(Book::Comments).string())
.col(ColumnDef::new(Book::UserId).integer().not_null())
.foreign_key(
ForeignKey::create()
.name("FK_2e303c3a712662f1fc2a4d0aavc")
.from(Book::Table, Book::UserId)
.to(User::Table, User::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade),
)
.to_owned(), .to_owned(),
) )
.await.expect("Migration failed"); .await
.expect("Migration failed");
manager manager
.create_table( .create_table(
@@ -62,7 +96,8 @@ impl MigrationTrait for Migration {
) )
.to_owned(), .to_owned(),
) )
.await.expect("Migration failed"); .await
.expect("Migration failed");
manager manager
.create_table( .create_table(
@@ -88,7 +123,8 @@ impl MigrationTrait for Migration {
) )
.to_owned(), .to_owned(),
) )
.await.expect("Migration failed"); .await
.expect("Migration failed");
manager manager
.create_table( .create_table(
@@ -114,7 +150,8 @@ impl MigrationTrait for Migration {
) )
.to_owned(), .to_owned(),
) )
.await.expect("Migration failed"); .await
.expect("Migration failed");
manager manager
.create_table( .create_table(
@@ -140,7 +177,8 @@ impl MigrationTrait for Migration {
) )
.to_owned(), .to_owned(),
) )
.await.expect("Migration failed"); .await
.expect("Migration failed");
manager manager
.create_table( .create_table(
@@ -166,7 +204,8 @@ impl MigrationTrait for Migration {
) )
.to_owned(), .to_owned(),
) )
.await.expect("Migration failed"); .await
.expect("Migration failed");
manager manager
.create_table( .create_table(
@@ -201,22 +240,28 @@ impl MigrationTrait for Migration {
manager manager
.drop_table(Table::drop().table(Book::Table).to_owned()) .drop_table(Table::drop().table(Book::Table).to_owned())
.await.expect("Drop failed"); .await
.expect("Drop failed");
manager manager
.drop_table(Table::drop().table(BookAuthor::Table).to_owned()) .drop_table(Table::drop().table(BookAuthor::Table).to_owned())
.await.expect("Drop failed"); .await
.expect("Drop failed");
manager manager
.drop_table(Table::drop().table(BookPerson::Table).to_owned()) .drop_table(Table::drop().table(BookPerson::Table).to_owned())
.await.expect("Drop failed"); .await
.expect("Drop failed");
manager manager
.drop_table(Table::drop().table(BookPlace::Table).to_owned()) .drop_table(Table::drop().table(BookPlace::Table).to_owned())
.await.expect("Drop failed"); .await
.expect("Drop failed");
manager manager
.drop_table(Table::drop().table(BookSubject::Table).to_owned()) .drop_table(Table::drop().table(BookSubject::Table).to_owned())
.await.expect("Drop failed"); .await
.expect("Drop failed");
manager manager
.drop_table(Table::drop().table(BookTime::Table).to_owned()) .drop_table(Table::drop().table(BookTime::Table).to_owned())
.await.expect("Drop failed"); .await
.expect("Drop failed");
manager manager
.drop_table(Table::drop().table(BookISBN::Table).to_owned()) .drop_table(Table::drop().table(BookISBN::Table).to_owned())
.await .await
@@ -246,6 +291,7 @@ enum Book {
TimeAdded, TimeAdded,
Rating, Rating,
Comments, Comments,
UserId,
} }
#[derive(Iden)] #[derive(Iden)]
@@ -295,3 +341,11 @@ enum BookISBN {
ISBN, ISBN,
BookId, BookId,
} }
#[derive(Iden)]
enum User {
Table,
Id,
UserName,
PasswordHash,
}

View File

@@ -8,9 +8,12 @@ publish = false
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
entity = { path = "../entity" } entity = { path = "../entity" }
chrono = "0.4" chrono = "0.4"
axum-login = "0.4"
axum = "^0.6"
eyre = "0.6.8"
[dependencies.sea-orm] [dependencies.sea-orm]
version = "^0.9.2" # sea-orm version version = "^0.10.6" # sea-orm version
features = [ features = [
"debug-print", "debug-print",
"runtime-tokio-native-tls", "runtime-tokio-native-tls",

View File

@@ -1,7 +1,9 @@
mod mutation; mod mutation;
mod query; mod query;
mod userstore;
pub use mutation::*; pub use mutation::*;
pub use query::*; pub use query::*;
pub use userstore::*;
pub use sea_orm; pub use sea_orm;

View File

@@ -1,15 +1,40 @@
//use ::entity::entities::prelude::Book; //use ::entity::entities::prelude::Book;
//use ::entity::entities::{prelude::*, *}; //use ::entity::entities::{prelude::*, *};
use ::entity::entities::{book::Entity as Book}; use ::entity::entities::book::Entity as Book;
use ::entity::entities::user::Entity as User;
//, book_author::Entity as Author, book_person::Entity as Person, book_place::Entity as Place, book_subject::Entity as Subject, book_time::Entity as Time, book_isbn::Entity as ISBN}; //, book_author::Entity as Author, book_person::Entity as Person, book_place::Entity as Place, book_subject::Entity as Subject, book_time::Entity as Time, book_isbn::Entity as ISBN};
use ::entity::entities::{book,book_author,book_person,book_place,book_subject,book_time,book_isbn}; use ::entity::entities::{
book, book_author, book_isbn, book_person, book_place, book_subject, book_time, user,
};
use sea_orm::*; use sea_orm::*;
//use ::entity::entities::prelude::Book; //use ::entity::entities::prelude::Book;
pub struct Mutation; pub struct Mutation;
impl Mutation { impl Mutation {
pub async fn create_book( pub async fn create_user(
db: &DbConn,
form_data: user::Model,
) -> Result<InsertResult<user::ActiveModel>, DbErr> {
let record = user::ActiveModel {
user_name: Set(form_data.user_name.to_owned()),
password_hash: Set(form_data.password_hash.to_owned()),
..Default::default()
};
User::insert(record).exec(db).await
}
pub async fn delete_user(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
let user: user::ActiveModel = User::find_by_id(id)
.one(db)
.await?
.ok_or(DbErr::Custom("Cannot find user.".to_owned()))
.map(Into::into)?;
user.delete(db).await
}
pub async fn create_book(
db: &DbConn, db: &DbConn,
form_data: book::Model, form_data: book::Model,
) -> Result<InsertResult<book::ActiveModel>, DbErr> { ) -> Result<InsertResult<book::ActiveModel>, DbErr> {
@@ -26,12 +51,13 @@ impl Mutation {
time_added: Set(form_data.time_added.to_owned()), time_added: Set(form_data.time_added.to_owned()),
rating: Set(form_data.rating.to_owned()), rating: Set(form_data.rating.to_owned()),
comments: Set(form_data.comments.to_owned()), comments: Set(form_data.comments.to_owned()),
user_id: Set(form_data.user_id.to_owned()),
..Default::default() ..Default::default()
}; };
Book::insert(record).exec(db).await Book::insert(record).exec(db).await
} }
pub async fn create_book_author( pub async fn create_book_author(
db: &DbConn, db: &DbConn,
form_data: book_author::Model, form_data: book_author::Model,
) -> Result<book_author::ActiveModel, DbErr> { ) -> Result<book_author::ActiveModel, DbErr> {
@@ -44,7 +70,7 @@ impl Mutation {
.await .await
} }
pub async fn create_book_person( pub async fn create_book_person(
db: &DbConn, db: &DbConn,
form_data: book_person::Model, form_data: book_person::Model,
) -> Result<book_person::ActiveModel, DbErr> { ) -> Result<book_person::ActiveModel, DbErr> {
@@ -57,7 +83,7 @@ impl Mutation {
.await .await
} }
pub async fn create_book_place( pub async fn create_book_place(
db: &DbConn, db: &DbConn,
form_data: book_place::Model, form_data: book_place::Model,
) -> Result<book_place::ActiveModel, DbErr> { ) -> Result<book_place::ActiveModel, DbErr> {
@@ -69,7 +95,7 @@ impl Mutation {
.save(db) .save(db)
.await .await
} }
pub async fn create_book_subject( pub async fn create_book_subject(
db: &DbConn, db: &DbConn,
form_data: book_subject::Model, form_data: book_subject::Model,
) -> Result<book_subject::ActiveModel, DbErr> { ) -> Result<book_subject::ActiveModel, DbErr> {
@@ -82,7 +108,7 @@ impl Mutation {
.await .await
} }
pub async fn create_book_time( pub async fn create_book_time(
db: &DbConn, db: &DbConn,
form_data: book_time::Model, form_data: book_time::Model,
) -> Result<book_time::ActiveModel, DbErr> { ) -> Result<book_time::ActiveModel, DbErr> {
@@ -95,7 +121,7 @@ impl Mutation {
.await .await
} }
pub async fn create_book_isbn( pub async fn create_book_isbn(
db: &DbConn, db: &DbConn,
form_data: book_isbn::Model, form_data: book_isbn::Model,
) -> Result<book_isbn::ActiveModel, DbErr> { ) -> Result<book_isbn::ActiveModel, DbErr> {
@@ -113,7 +139,6 @@ impl Mutation {
id: i32, id: i32,
form_data: book::Model, form_data: book::Model,
) -> Result<book::Model, DbErr> { ) -> Result<book::Model, DbErr> {
let book: book::ActiveModel = Book::find_by_id(id) let book: book::ActiveModel = Book::find_by_id(id)
.one(db) .one(db)
.await? .await?
@@ -134,7 +159,10 @@ impl Mutation {
time_added: Set(form_data.time_added.to_owned()), time_added: Set(form_data.time_added.to_owned()),
rating: Set(form_data.rating.to_owned()), rating: Set(form_data.rating.to_owned()),
comments: Set(form_data.comments.to_owned()), comments: Set(form_data.comments.to_owned()),
}.update(db).await user_id: Set(form_data.user_id.to_owned()),
}
.update(db)
.await
} }
pub async fn delete_book(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> { pub async fn delete_book(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
@@ -147,54 +175,49 @@ impl Mutation {
book.delete(db).await book.delete(db).await
} }
pub async fn delete_book_author(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> { pub async fn delete_book_author(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
book_author::Entity::delete_many().filter( book_author::Entity::delete_many()
Condition::any() .filter(Condition::any().add(book_author::Column::BookId.eq(id)))
.add(book_author::Column::BookId.eq(id)) .exec(db)
).exec(db).await .await
} }
pub async fn delete_book_person(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> { pub async fn delete_book_person(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
book_person::Entity::delete_many().filter( book_person::Entity::delete_many()
Condition::any() .filter(Condition::any().add(book_person::Column::BookId.eq(id)))
.add(book_person::Column::BookId.eq(id)) .exec(db)
).exec(db).await .await
} }
pub async fn delete_book_place(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> { pub async fn delete_book_place(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
book_place::Entity::delete_many().filter( book_place::Entity::delete_many()
Condition::any() .filter(Condition::any().add(book_place::Column::BookId.eq(id)))
.add(book_place::Column::BookId.eq(id)) .exec(db)
).exec(db).await .await
} }
pub async fn delete_book_subject(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> { pub async fn delete_book_subject(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
book_subject::Entity::delete_many().filter( book_subject::Entity::delete_many()
Condition::any() .filter(Condition::any().add(book_subject::Column::BookId.eq(id)))
.add(book_subject::Column::BookId.eq(id)) .exec(db)
).exec(db).await .await
} }
pub async fn delete_book_time(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> { pub async fn delete_book_time(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
book_time::Entity::delete_many().filter( book_time::Entity::delete_many()
Condition::any() .filter(Condition::any().add(book_time::Column::BookId.eq(id)))
.add(book_time::Column::BookId.eq(id)) .exec(db)
).exec(db).await .await
} }
pub async fn delete_book_isbn(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> { pub async fn delete_book_isbn(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
book_isbn::Entity::delete_many().filter( book_isbn::Entity::delete_many()
Condition::any() .filter(Condition::any().add(book_isbn::Column::BookId.eq(id)))
.add(book_isbn::Column::BookId.eq(id)) .exec(db)
).exec(db).await .await
} }
pub async fn delete_all_books(db: &DbConn) -> Result<DeleteResult, DbErr> {
pub async fn delete_all_books(db: &DbConn) -> Result<DeleteResult, DbErr> {
Book::delete_many().exec(db).await Book::delete_many().exec(db).await
} }
} }

View File

@@ -1,34 +1,14 @@
use ::entity::entities::book::Entity as Book; use ::entity::entities::book::Entity as Book;
use ::entity::entities::user::Entity as User;
//, book_author::Entity as Author, book_person::Entity as Person, book_place::Entity as Place, book_subject::Entity as Subject, book_time::Entity as Time, book_isbn::Entity as ISBN}; //, book_author::Entity as Author, book_person::Entity as Person, book_place::Entity as Place, book_subject::Entity as Subject, book_time::Entity as Time, book_isbn::Entity as ISBN};
use ::entity::entities::{book,book_author,book_person,book_place,book_subject,book_time,book_isbn}; use ::entity::entities::{
book, book_author, book_isbn, book_person, book_place, book_subject, book_time, user,
};
//use ::entity::entities::{prelude::*, *}; //use ::entity::entities::{prelude::*, *};
use sea_orm::*; use sea_orm::*;
//use sea_query::Expr; //use sea_query::Expr;
#[derive(FromQueryResult,Clone)]
pub struct BookAndMeta {
pub id: i32,
pub open_library_key: String,
pub title: String,
pub edition_count: i32,
pub first_publish_year: i32,
pub median_page_count: i32,
pub goodread_id: String,
pub description: String,
pub cover: String,
pub location: String,
pub time_added: String,
pub rating: i32,
pub comments: String,
pub author_name: String,
pub person: String,
pub place: String,
pub subject: String,
pub time: String,
pub isbn: String,
}
#[derive(Clone)] #[derive(Clone)]
pub struct BookAndMetaV2 { pub struct BookAndMetaV2 {
pub book: book::Model, pub book: book::Model,
@@ -48,62 +28,104 @@ impl Query {
Book::find_by_id(id).one(db).await Book::find_by_id(id).one(db).await
} }
/* pub async fn find_bookplusmeta_by_id(db: &DbConn, id: i32) -> Result<Option<(book::Model, Vec<book_author::Model>, Vec<book_person::Model>)>, DbErr> { pub async fn find_userid_by_name(
let book = Book::find_by_id(id).one(db).await?.unwrap(); db: &DbConn,
let authors = book.find_related(Author).all(db).await?; name: String,
let persons = book.find_related(Person).all(db).await?; ) -> Result<Option<user::Model>, DbErr> {
Ok(Some((book, authors, persons))) User::find()
.filter(user::Column::UserName.eq(name))
.one(db)
.await
} }
*/
pub async fn find_user_by_id(db: &DbConn, id: i32) -> Result<Option<user::Model>, DbErr> {
User::find_by_id(id).one(db).await
}
pub async fn list_all_users(db: &DbConn) -> Result<Vec<user::Model>, DbErr> {
User::find().all(db).await
}
/* pub async fn find_bookplusmeta_by_id(db: &DbConn, id: i32) -> Result<Option<(book::Model, Vec<book_author::Model>, Vec<book_person::Model>)>, DbErr> {
let book = Book::find_by_id(id).one(db).await?.unwrap();
let authors = book.find_related(Author).all(db).await?;
let persons = book.find_related(Person).all(db).await?;
Ok(Some((book, authors, persons)))
}
*/
/// If ok, returns (post models, num pages). /// If ok, returns (post models, num pages).
pub async fn find_books_in_page( pub async fn find_books_in_page(
db: &DbConn, db: &DbConn,
page: usize, page: u64,
posts_per_page: usize, posts_per_page: u64,
) -> Result<(Vec<book::Model>, usize), DbErr> { userid: i32,
sort: String,
) -> Result<(Vec<book::Model>, u64), DbErr> {
// Setup paginator // Setup paginator
let paginator = Book::find() if sort == "desc".to_string() {
.order_by_asc(book::Column::Id) let paginator = Book::find()
.paginate(db, posts_per_page); .filter(book::Column::UserId.eq(userid))
let num_pages = paginator.num_pages().await?; .order_by_desc(book::Column::Id)
.paginate(db, posts_per_page);
let num_pages = paginator.num_pages().await?;
return paginator.fetch_page(page - 1).await.map(|p| (p, num_pages));
} else {
let paginator = Book::find()
.filter(book::Column::UserId.eq(userid))
.order_by_asc(book::Column::Id)
.paginate(db, posts_per_page);
let num_pages = paginator.num_pages().await?;
return paginator.fetch_page(page - 1).await.map(|p| (p, num_pages));
}
// Fetch paginated posts // Fetch paginated posts
paginator.fetch_page(page - 1).await.map(|p| (p, num_pages))
} }
pub async fn find_books_plus_meta_in_page( pub async fn find_books_plus_meta_in_page(
db: &DbConn, db: &DbConn,
page: usize, page: usize,
posts_per_page: usize, posts_per_page: usize,
) -> Result<(Vec<BookAndMetaV2>, usize), DbErr> { userid: i32,
sort: String,
) -> Result<(Vec<BookAndMetaV2>, u64), DbErr> {
// Setup paginator // Setup paginator
let books = Self::find_books_in_page(db,page,posts_per_page).await?; let books = Self::find_books_in_page(
db,
page.try_into().unwrap(),
posts_per_page.try_into().unwrap(),
userid,
sort,
)
.await?;
let book_ids: Vec<i32> = books.0.clone().into_iter().map(|b| b.id).collect(); let book_ids: Vec<i32> = books.0.clone().into_iter().map(|b| b.id).collect();
println!("SIZE IS {} and {:?}", book_ids.len(), book_ids);
let mut resbooks: Vec<BookAndMetaV2> = Vec::with_capacity(book_ids.len()); let mut resbooks: Vec<BookAndMetaV2> = Vec::with_capacity(book_ids.len());
for book in books.0.iter() { for book in books.0.iter() {
let bauthors: Vec<book_author::Model> = book.find_related(book_author::Entity).all(db).await?; let bauthors: Vec<book_author::Model> =
let bpersons: Vec<book_person::Model> = book.find_related(book_person::Entity).all(db).await?; book.find_related(book_author::Entity).all(db).await?;
let bplaces: Vec<book_place::Model> = book.find_related(book_place::Entity).all(db).await?; let bpersons: Vec<book_person::Model> =
let bsubjects: Vec<book_subject::Model> = book.find_related(book_subject::Entity).all(db).await?; book.find_related(book_person::Entity).all(db).await?;
let btimes: Vec<book_time::Model> = book.find_related(book_time::Entity).all(db).await?; let bplaces: Vec<book_place::Model> =
let bisbns: Vec<book_isbn::Model> = book.find_related(book_isbn::Entity).all(db).await?; book.find_related(book_place::Entity).all(db).await?;
let bsubjects: Vec<book_subject::Model> =
book.find_related(book_subject::Entity).all(db).await?;
let btimes: Vec<book_time::Model> =
book.find_related(book_time::Entity).all(db).await?;
let bisbns: Vec<book_isbn::Model> =
book.find_related(book_isbn::Entity).all(db).await?;
let bookandmeta = BookAndMetaV2 { let bookandmeta = BookAndMetaV2 {
book: (book.clone()), book: (book.clone()),
authors: bauthors, authors: bauthors,
persons: bpersons, persons: bpersons,
places: bplaces, places: bplaces,
subjects: bsubjects, subjects: bsubjects,
times: btimes, times: btimes,
isbns: bisbns isbns: bisbns,
}; };
resbooks.push(bookandmeta); resbooks.push(bookandmeta);
} }
Ok((resbooks,books.1)) Ok((resbooks, books.1))
} }
} }

View File

@@ -0,0 +1,60 @@
use crate::Query;
use axum::async_trait;
use axum_login::{secrecy::SecretVec, AuthUser, UserStore};
use sea_orm::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AxumUser {
pub id: i32,
pub password_hash: String,
pub name: String,
}
type Result<T = ()> = std::result::Result<T, eyre::Error>;
impl<Role> AuthUser<Role> for AxumUser
where
Role: PartialOrd + PartialEq + Clone + Send + Sync + 'static,
{
fn get_id(&self) -> String {
format!("{}", self.id)
}
fn get_password_hash(&self) -> axum_login::secrecy::SecretVec<u8> {
SecretVec::new(self.password_hash.clone().into())
}
}
#[derive(Debug, Clone)]
pub struct AxumUserStore {
conn: DatabaseConnection,
}
impl AxumUserStore {
pub fn new(conn: &DatabaseConnection) -> Self {
Self { conn: conn.clone() }
}
}
#[async_trait]
impl<Role> UserStore<Role> for AxumUserStore
where
Role: PartialOrd + PartialEq + Clone + Send + Sync + 'static,
{
type User = AxumUser;
async fn load_user(&self, user_id: &str) -> Result<Option<Self::User>> {
// my user id is a Vec<u8>, so it's stored base64 encoded
let id: i32 = user_id.parse().unwrap();
let user = Query::find_user_by_id(&self.conn, id).await?;
match user {
Some(u) => Ok(Some(AxumUser {
id: u.id,
password_hash: u.password_hash,
name: u.user_name.unwrap(),
})),
None => Ok(None),
}
}
}

View File

@@ -1,6 +1,6 @@
use meilisearch_sdk::client::*; use meilisearch_sdk::client::*;
use meilisearch_sdk::search::*; use meilisearch_sdk::search::*;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
pub struct BookMeili { pub struct BookMeili {
@@ -25,35 +25,37 @@ pub struct BookMeili {
pub isbn: Vec<String>, pub isbn: Vec<String>,
} }
pub async fn create_or_update_book(book: BookMeili, client: &Client) { pub async fn create_or_update_book(book: BookMeili, userid: i32, client: &Client) {
// An index is where the documents are stored. // An index is where the documents are stored.
let books = client.index("books"); let books = client.index(format!("books{}", userid));
// Add some movies in the index. If the index 'movies' does not exist, Meilisearch creates it when you first add the documents. // Add some movies in the index. If the index 'movies' does not exist, Meilisearch creates it when you first add the documents.
books.add_or_replace(&[ books.add_or_replace(&[book], Some("id")).await.unwrap();
book
], Some("id")).await.unwrap();
} }
pub async fn delete_book(bookid: i32, userid: i32, client: &Client) {
pub async fn delete_book(bookid: i32, client: &Client) {
// An index is where the documents are stored. // An index is where the documents are stored.
let books = client.index("books"); let books = client.index(format!("books{}", userid));
books.delete_document(bookid).await.unwrap(); books.delete_document(bookid).await.unwrap();
} }
pub async fn search_book(
pub async fn search_book(search: &str, page: usize, client: &Client) -> Result<(Vec<BookMeili>, usize), meilisearch_sdk::errors::Error> { search: &str,
page: usize,
userid: i32,
client: &Client,
) -> Result<(Vec<BookMeili>, usize), meilisearch_sdk::errors::Error> {
// An index is where the documents are stored. // An index is where the documents are stored.
let books = client.index("books"); let books = client.index(format!("books{}", userid));
let results : SearchResults<BookMeili> = books.search().with_query(search).with_offset((page-1)*12) let results: SearchResults<BookMeili> = books
.execute::<BookMeili>().await.unwrap(); .search()
.with_query(search)
.with_offset((page - 1) * 12)
.execute::<BookMeili>()
.await
.unwrap();
let formatted_results : Vec<BookMeili> = (results.hits).iter().map(|r| r.result.clone()).collect(); let formatted_results: Vec<BookMeili> =
//.iter()s.map(|r| r.formatted_result.unwrap()).collect(); (results.hits).iter().map(|r| r.result.clone()).collect();
return Ok((formatted_results, results.estimated_total_hits)); //.iter()s.map(|r| r.formatted_result.unwrap()).collect();
return Ok((formatted_results, results.estimated_total_hits));
} }

View File

@@ -1,4 +1,3 @@
fn main() { fn main() {
booksman_api::main(); booksman_api::main();
} }

52
frontend/Cargo.lock generated
View File

@@ -136,7 +136,6 @@ dependencies = [
"serde-wasm-bindgen", "serde-wasm-bindgen",
"serde_json", "serde_json",
"sycamore", "sycamore",
"sycamore-router",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-logger", "wasm-logger",
@@ -381,22 +380,6 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.13.1" version = "1.13.1"
@@ -565,9 +548,9 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
[[package]] [[package]]
name = "sycamore" name = "sycamore"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4decd3fabbb4cfa8ef4d8b4469c7d35d65555806f6c6642a2733d892472ffa" checksum = "67817393b3c9828db84614f64db9a1ebb94729ce3a3751c41e7ff23d3f8e7f00"
dependencies = [ dependencies = [
"ahash", "ahash",
"futures", "futures",
@@ -631,31 +614,6 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "sycamore-router"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123b34a150dac877d7bfae82dadfb0c586fd35a8f5fcdf1721dafa079fdc4c40"
dependencies = [
"sycamore",
"sycamore-router-macro",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "sycamore-router-macro"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92914a2f809b636d245b28d8a734801ecb8ff9c4996bbe6ea4176582e12503eb"
dependencies = [
"nom",
"proc-macro2",
"quote",
"syn",
"unicode-xid",
]
[[package]] [[package]]
name = "sycamore-web" name = "sycamore-web"
version = "0.8.0" version = "0.8.0"
@@ -728,12 +686,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "unicode-xid"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]] [[package]]
name = "utf8-width" name = "utf8-width"
version = "0.1.6" version = "0.1.6"

View File

@@ -23,13 +23,13 @@ wasm-logger = "0.2.0"
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde-wasm-bindgen = "0.4" serde-wasm-bindgen = "0.4"
sycamore = {version = "0.8.0-beta.7", features = ["suspense"]} sycamore = {version = "0.8.2", features = ["suspense"]}
sycamore-router = "0.8.0-beta.7" #sycamore-router = "0.8.2"
wasm-bindgen = "0.2.79" wasm-bindgen = "0.2.79"
#tokio = {version = "1.21.2", features = ["full"] } #tokio = {version = "1.21.2", features = ["full"] }
#yew = "0.19.3" #yew = "0.19.3"
#yew-router = "0.16.0" #yew-router = "0.16.0"
[dependencies.web-sys] [dependencies.web-sys]
features = ["InputEvent", "KeyboardEvent", "Location", "Storage"] features = ["InputEvent", "EventTarget", "MouseEvent", "KeyboardEvent", "Location", "Storage", "Element", "Window", "Document"]
version = "0.3.56" version = "0.3.56"

View File

@@ -3,5 +3,12 @@ target = "index.html"
dist = "../dist" dist = "../dist"
public_url = "/assets/" public_url = "/assets/"
[[hooks]]
stage = "build"
command = "sh"
command_arguments = ["-c", "npx tailwindcss -i src/tailwind.css -o $TRUNK_STAGING_DIR/tailwind.css"]
[[proxy]] [[proxy]]
backend = "http://[::1]:8081/api/" backend = "http://[::1]:8081/api/"

View File

@@ -36,179 +36,3 @@ body {
overflow: auto; /* Enable scroll if needed */ overflow: auto; /* Enable scroll if needed */
background-color: #f1f1f1; /* Fallback color */ background-color: #f1f1f1; /* Fallback color */
} }
/* Float three header columns side by side */
.header {
position: fixed; /* Stay in place */
overflow: hidden;
z-index: 1; /* Sit on top */
width: 100%;
top: 0; /* Position the navbar at the top of the page */
padding: 0 10px;
background-color: #f1f1f1;
}
.main {
margin-top: 50px; /* Add a top margin to avoid content overlay */
}
/* Float three header columns side by side */
.header-column {
float: left;
width: 28%;
padding: 0 10px;
}
/* Float three header columns side by side */
.header-page-column {
float: left;
margin-top: 0px;
width: 15%;
padding: 0 10px;
}
/* Float four columns side by side */
.column {
float: left;
width: 25%;
padding: 0 10px;
margin-top: 10px;
margin-bottom: 10px;
}
/* Remove extra left and right margins, due to padding in columns */
.row {margin: 0 -5px;}
/* Clear floats after the columns */
.row:after {
content: "";
display: table;
clear: both;
}
/* Style the counter cards */
.card {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); /* this adds the "card" effect */
padding: 16px;
text-align: left;
height: 400px;
overflow: hidden;
text-overflow: ellipsis;
background-color: #f1f1f1;
}
/* Style the counter cards */
.card-openlibrary {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); /* this adds the "card" effect */
padding: 16px;
text-align: left;
height: 200px;
overflow: hidden;
text-overflow: ellipsis;
background-color: #f1f1f1;
}
.card img {
float: right;
width: 100px;
padding: 0 20px 20px 0;
}
.card-openlibrary img {
float: right;
width: 100px;
padding: 0 20px 20px 0;
}
.card-title {
padding: 16px;
text-align: left;
width: 60%;
font-weight: bold;
font-size: large;
}
.card-authors {
padding: 16px;
text-align: left;
width: 60%;
font-size: large;
}
.card-desc {
padding: 2px;
text-align: left;
width: 100%;
}
.input-field {
padding: 4px;
float: left;
text-align: left;
width: 50%;
font-size: large;
}
.input-field label {
display: block;
vertical-align: middle;
min-width: 20%;
max-width: 20%;
width: 20%;
padding: 4px;
}
.input-field textarea {
vertical-align: middle;
width: 80%;
}
.input-buttons {
padding: 20px;
float: left;
text-align: left;
width: 100%;
}
.more-info {
width: 90%;
}
.more-info img {
float: right;
width: 200px;
padding: 0 20px 20px 0;
}
.more-info label {
display:inline-block;
text-align: left;
width: 20%;
font-weight: bold;
font-size: large;
}
.more-info-buttons {
padding: 20px;
text-align: left;
}
.page-input {
width: 40px;
}
/* Responsive columns - one column layout (vertical) on small screens */
@media screen and (max-width: 600px) {
.column {
width: 100%;
display: block;
margin-bottom: 20px;
}
}
@media screen and (max-width: 800px) {
.main {
margin-top: 80px; /* Add a top margin to avoid content overlay */
}
.input-field {
width: 100%;
}
}

View File

@@ -2,9 +2,14 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link data-trunk rel="css" href="css/index.css" /> <link rel="shortcut icon" type="image/x-icon" href="/images/favicon.svg">
<base data-trunk-public-url/> <link data-trunk rel="css" href="css/index.css" />
<link data-trunk rel="copy-dir" href="static" />
<link rel="stylesheet" href="/assets/tailwind.css"/>
<link rel="stylesheet" href="/assets/static/fontawesome/css/fontawesome.css"/>
<link rel="stylesheet" href="/assets/static/fontawesome/css/solid.css"/>
<base data-trunk-public-url/>
<title>Book Manager</title> <title>Book Manager</title>
</head> </head>
<body></body> <body></body>

1368
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

6
frontend/package.json Normal file
View File

@@ -0,0 +1,6 @@
{
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"tailwindcss": "^3.2.4"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

7946
frontend/static/fontawesome/css/all.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

1516
frontend/static/fontawesome/css/brands.css vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,19 @@
/*!
* Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2022 Fonticons, Inc.
*/
:root, :host {
--fa-style-family-classic: 'Font Awesome 6 Free';
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; }
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 400;
font-display: block;
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }
.far,
.fa-regular {
font-weight: 400; }

View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2022 Fonticons, Inc.
*/
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}

View File

@@ -0,0 +1,19 @@
/*!
* Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2022 Fonticons, Inc.
*/
:root, :host {
--fa-style-family-classic: 'Font Awesome 6 Free';
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; }
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 900;
font-display: block;
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
.fas,
.fa-solid {
font-weight: 900; }

View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2022 Fonticons, Inc.
*/
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}

View File

@@ -0,0 +1,635 @@
/*!
* Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2022 Fonticons, Inc.
*/
:root, :host {
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Solid';
--fa-font-regular: normal 400 1em/1 'Font Awesome 6 Regular';
--fa-font-light: normal 300 1em/1 'Font Awesome 6 Light';
--fa-font-thin: normal 100 1em/1 'Font Awesome 6 Thin';
--fa-font-duotone: normal 900 1em/1 'Font Awesome 6 Duotone';
--fa-font-sharp-solid: normal 900 1em/1 'Font Awesome 6 Sharp';
--fa-font-brands: normal 400 1em/1 'Font Awesome 6 Brands'; }
svg:not(:root).svg-inline--fa, svg:not(:host).svg-inline--fa {
overflow: visible;
box-sizing: content-box; }
.svg-inline--fa {
display: var(--fa-display, inline-block);
height: 1em;
overflow: visible;
vertical-align: -.125em; }
.svg-inline--fa.fa-2xs {
vertical-align: 0.1em; }
.svg-inline--fa.fa-xs {
vertical-align: 0em; }
.svg-inline--fa.fa-sm {
vertical-align: -0.07143em; }
.svg-inline--fa.fa-lg {
vertical-align: -0.2em; }
.svg-inline--fa.fa-xl {
vertical-align: -0.25em; }
.svg-inline--fa.fa-2xl {
vertical-align: -0.3125em; }
.svg-inline--fa.fa-pull-left {
margin-right: var(--fa-pull-margin, 0.3em);
width: auto; }
.svg-inline--fa.fa-pull-right {
margin-left: var(--fa-pull-margin, 0.3em);
width: auto; }
.svg-inline--fa.fa-li {
width: var(--fa-li-width, 2em);
top: 0.25em; }
.svg-inline--fa.fa-fw {
width: var(--fa-fw-width, 1.25em); }
.fa-layers svg.svg-inline--fa {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0; }
.fa-layers-text, .fa-layers-counter {
display: inline-block;
position: absolute;
text-align: center; }
.fa-layers {
display: inline-block;
height: 1em;
position: relative;
text-align: center;
vertical-align: -.125em;
width: 1em; }
.fa-layers svg.svg-inline--fa {
-webkit-transform-origin: center center;
transform-origin: center center; }
.fa-layers-text {
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
-webkit-transform-origin: center center;
transform-origin: center center; }
.fa-layers-counter {
background-color: var(--fa-counter-background-color, #ff253a);
border-radius: var(--fa-counter-border-radius, 1em);
box-sizing: border-box;
color: var(--fa-inverse, #fff);
line-height: var(--fa-counter-line-height, 1);
max-width: var(--fa-counter-max-width, 5em);
min-width: var(--fa-counter-min-width, 1.5em);
overflow: hidden;
padding: var(--fa-counter-padding, 0.25em 0.5em);
right: var(--fa-right, 0);
text-overflow: ellipsis;
top: var(--fa-top, 0);
-webkit-transform: scale(var(--fa-counter-scale, 0.25));
transform: scale(var(--fa-counter-scale, 0.25));
-webkit-transform-origin: top right;
transform-origin: top right; }
.fa-layers-bottom-right {
bottom: var(--fa-bottom, 0);
right: var(--fa-right, 0);
top: auto;
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
transform: scale(var(--fa-layers-scale, 0.25));
-webkit-transform-origin: bottom right;
transform-origin: bottom right; }
.fa-layers-bottom-left {
bottom: var(--fa-bottom, 0);
left: var(--fa-left, 0);
right: auto;
top: auto;
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
transform: scale(var(--fa-layers-scale, 0.25));
-webkit-transform-origin: bottom left;
transform-origin: bottom left; }
.fa-layers-top-right {
top: var(--fa-top, 0);
right: var(--fa-right, 0);
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
transform: scale(var(--fa-layers-scale, 0.25));
-webkit-transform-origin: top right;
transform-origin: top right; }
.fa-layers-top-left {
left: var(--fa-left, 0);
right: auto;
top: var(--fa-top, 0);
-webkit-transform: scale(var(--fa-layers-scale, 0.25));
transform: scale(var(--fa-layers-scale, 0.25));
-webkit-transform-origin: top left;
transform-origin: top left; }
.fa-1x {
font-size: 1em; }
.fa-2x {
font-size: 2em; }
.fa-3x {
font-size: 3em; }
.fa-4x {
font-size: 4em; }
.fa-5x {
font-size: 5em; }
.fa-6x {
font-size: 6em; }
.fa-7x {
font-size: 7em; }
.fa-8x {
font-size: 8em; }
.fa-9x {
font-size: 9em; }
.fa-10x {
font-size: 10em; }
.fa-2xs {
font-size: 0.625em;
line-height: 0.1em;
vertical-align: 0.225em; }
.fa-xs {
font-size: 0.75em;
line-height: 0.08333em;
vertical-align: 0.125em; }
.fa-sm {
font-size: 0.875em;
line-height: 0.07143em;
vertical-align: 0.05357em; }
.fa-lg {
font-size: 1.25em;
line-height: 0.05em;
vertical-align: -0.075em; }
.fa-xl {
font-size: 1.5em;
line-height: 0.04167em;
vertical-align: -0.125em; }
.fa-2xl {
font-size: 2em;
line-height: 0.03125em;
vertical-align: -0.1875em; }
.fa-fw {
text-align: center;
width: 1.25em; }
.fa-ul {
list-style-type: none;
margin-left: var(--fa-li-margin, 2.5em);
padding-left: 0; }
.fa-ul > li {
position: relative; }
.fa-li {
left: calc(var(--fa-li-width, 2em) * -1);
position: absolute;
text-align: center;
width: var(--fa-li-width, 2em);
line-height: inherit; }
.fa-border {
border-color: var(--fa-border-color, #eee);
border-radius: var(--fa-border-radius, 0.1em);
border-style: var(--fa-border-style, solid);
border-width: var(--fa-border-width, 0.08em);
padding: var(--fa-border-padding, 0.2em 0.25em 0.15em); }
.fa-pull-left {
float: left;
margin-right: var(--fa-pull-margin, 0.3em); }
.fa-pull-right {
float: right;
margin-left: var(--fa-pull-margin, 0.3em); }
.fa-beat {
-webkit-animation-name: fa-beat;
animation-name: fa-beat;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out);
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
.fa-bounce {
-webkit-animation-name: fa-bounce;
animation-name: fa-bounce;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1));
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); }
.fa-fade {
-webkit-animation-name: fa-fade;
animation-name: fa-fade;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
.fa-beat-fade {
-webkit-animation-name: fa-beat-fade;
animation-name: fa-beat-fade;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));
animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); }
.fa-flip {
-webkit-animation-name: fa-flip;
animation-name: fa-flip;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out);
animation-timing-function: var(--fa-animation-timing, ease-in-out); }
.fa-shake {
-webkit-animation-name: fa-shake;
animation-name: fa-shake;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, linear);
animation-timing-function: var(--fa-animation-timing, linear); }
.fa-spin {
-webkit-animation-name: fa-spin;
animation-name: fa-spin;
-webkit-animation-delay: var(--fa-animation-delay, 0s);
animation-delay: var(--fa-animation-delay, 0s);
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 2s);
animation-duration: var(--fa-animation-duration, 2s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, linear);
animation-timing-function: var(--fa-animation-timing, linear); }
.fa-spin-reverse {
--fa-animation-direction: reverse; }
.fa-pulse,
.fa-spin-pulse {
-webkit-animation-name: fa-spin;
animation-name: fa-spin;
-webkit-animation-direction: var(--fa-animation-direction, normal);
animation-direction: var(--fa-animation-direction, normal);
-webkit-animation-duration: var(--fa-animation-duration, 1s);
animation-duration: var(--fa-animation-duration, 1s);
-webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite);
animation-iteration-count: var(--fa-animation-iteration-count, infinite);
-webkit-animation-timing-function: var(--fa-animation-timing, steps(8));
animation-timing-function: var(--fa-animation-timing, steps(8)); }
@media (prefers-reduced-motion: reduce) {
.fa-beat,
.fa-bounce,
.fa-fade,
.fa-beat-fade,
.fa-flip,
.fa-pulse,
.fa-shake,
.fa-spin,
.fa-spin-pulse {
-webkit-animation-delay: -1ms;
animation-delay: -1ms;
-webkit-animation-duration: 1ms;
animation-duration: 1ms;
-webkit-animation-iteration-count: 1;
animation-iteration-count: 1;
transition-delay: 0s;
transition-duration: 0s; } }
@-webkit-keyframes fa-beat {
0%, 90% {
-webkit-transform: scale(1);
transform: scale(1); }
45% {
-webkit-transform: scale(var(--fa-beat-scale, 1.25));
transform: scale(var(--fa-beat-scale, 1.25)); } }
@keyframes fa-beat {
0%, 90% {
-webkit-transform: scale(1);
transform: scale(1); }
45% {
-webkit-transform: scale(var(--fa-beat-scale, 1.25));
transform: scale(var(--fa-beat-scale, 1.25)); } }
@-webkit-keyframes fa-bounce {
0% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); }
10% {
-webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
30% {
-webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
50% {
-webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
57% {
-webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
64% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); }
100% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); } }
@keyframes fa-bounce {
0% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); }
10% {
-webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);
transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); }
30% {
-webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));
transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); }
50% {
-webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);
transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); }
57% {
-webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));
transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); }
64% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); }
100% {
-webkit-transform: scale(1, 1) translateY(0);
transform: scale(1, 1) translateY(0); } }
@-webkit-keyframes fa-fade {
50% {
opacity: var(--fa-fade-opacity, 0.4); } }
@keyframes fa-fade {
50% {
opacity: var(--fa-fade-opacity, 0.4); } }
@-webkit-keyframes fa-beat-fade {
0%, 100% {
opacity: var(--fa-beat-fade-opacity, 0.4);
-webkit-transform: scale(1);
transform: scale(1); }
50% {
opacity: 1;
-webkit-transform: scale(var(--fa-beat-fade-scale, 1.125));
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
@keyframes fa-beat-fade {
0%, 100% {
opacity: var(--fa-beat-fade-opacity, 0.4);
-webkit-transform: scale(1);
transform: scale(1); }
50% {
opacity: 1;
-webkit-transform: scale(var(--fa-beat-fade-scale, 1.125));
transform: scale(var(--fa-beat-fade-scale, 1.125)); } }
@-webkit-keyframes fa-flip {
50% {
-webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
@keyframes fa-flip {
50% {
-webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));
transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } }
@-webkit-keyframes fa-shake {
0% {
-webkit-transform: rotate(-15deg);
transform: rotate(-15deg); }
4% {
-webkit-transform: rotate(15deg);
transform: rotate(15deg); }
8%, 24% {
-webkit-transform: rotate(-18deg);
transform: rotate(-18deg); }
12%, 28% {
-webkit-transform: rotate(18deg);
transform: rotate(18deg); }
16% {
-webkit-transform: rotate(-22deg);
transform: rotate(-22deg); }
20% {
-webkit-transform: rotate(22deg);
transform: rotate(22deg); }
32% {
-webkit-transform: rotate(-12deg);
transform: rotate(-12deg); }
36% {
-webkit-transform: rotate(12deg);
transform: rotate(12deg); }
40%, 100% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); } }
@keyframes fa-shake {
0% {
-webkit-transform: rotate(-15deg);
transform: rotate(-15deg); }
4% {
-webkit-transform: rotate(15deg);
transform: rotate(15deg); }
8%, 24% {
-webkit-transform: rotate(-18deg);
transform: rotate(-18deg); }
12%, 28% {
-webkit-transform: rotate(18deg);
transform: rotate(18deg); }
16% {
-webkit-transform: rotate(-22deg);
transform: rotate(-22deg); }
20% {
-webkit-transform: rotate(22deg);
transform: rotate(22deg); }
32% {
-webkit-transform: rotate(-12deg);
transform: rotate(-12deg); }
36% {
-webkit-transform: rotate(12deg);
transform: rotate(12deg); }
40%, 100% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); } }
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
.fa-rotate-90 {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); }
.fa-rotate-180 {
-webkit-transform: rotate(180deg);
transform: rotate(180deg); }
.fa-rotate-270 {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); }
.fa-flip-horizontal {
-webkit-transform: scale(-1, 1);
transform: scale(-1, 1); }
.fa-flip-vertical {
-webkit-transform: scale(1, -1);
transform: scale(1, -1); }
.fa-flip-both,
.fa-flip-horizontal.fa-flip-vertical {
-webkit-transform: scale(-1, -1);
transform: scale(-1, -1); }
.fa-rotate-by {
-webkit-transform: rotate(var(--fa-rotate-angle, none));
transform: rotate(var(--fa-rotate-angle, none)); }
.fa-stack {
display: inline-block;
vertical-align: middle;
height: 2em;
position: relative;
width: 2.5em; }
.fa-stack-1x,
.fa-stack-2x {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0;
z-index: var(--fa-stack-z-index, auto); }
.svg-inline--fa.fa-stack-1x {
height: 1em;
width: 1.25em; }
.svg-inline--fa.fa-stack-2x {
height: 2em;
width: 2.5em; }
.fa-inverse {
color: var(--fa-inverse, #fff); }
.sr-only,
.fa-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0; }
.sr-only-focusable:not(:focus),
.fa-sr-only-focusable:not(:focus) {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0; }
.svg-inline--fa .fa-primary {
fill: var(--fa-primary-color, currentColor);
opacity: var(--fa-primary-opacity, 1); }
.svg-inline--fa .fa-secondary {
fill: var(--fa-secondary-color, currentColor);
opacity: var(--fa-secondary-opacity, 0.4); }
.svg-inline--fa.fa-swap-opacity .fa-primary {
opacity: var(--fa-secondary-opacity, 0.4); }
.svg-inline--fa.fa-swap-opacity .fa-secondary {
opacity: var(--fa-primary-opacity, 1); }
.svg-inline--fa mask .fa-primary,
.svg-inline--fa mask .fa-secondary {
fill: black; }
.fad.fa-inverse,
.fa-duotone.fa-inverse {
color: var(--fa-inverse, #fff); }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,26 @@
/*!
* Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2022 Fonticons, Inc.
*/
@font-face {
font-family: 'FontAwesome';
font-display: block;
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
@font-face {
font-family: 'FontAwesome';
font-display: block;
src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
@font-face {
font-family: 'FontAwesome';
font-display: block;
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype");
unicode-range: U+F003,U+F006,U+F014,U+F016-F017,U+F01A-F01B,U+F01D,U+F022,U+F03E,U+F044,U+F046,U+F05C-F05D,U+F06E,U+F070,U+F087-F088,U+F08A,U+F094,U+F096-F097,U+F09D,U+F0A0,U+F0A2,U+F0A4-F0A7,U+F0C5,U+F0C7,U+F0E5-F0E6,U+F0EB,U+F0F6-F0F8,U+F10C,U+F114-F115,U+F118-F11A,U+F11C-F11D,U+F133,U+F147,U+F14E,U+F150-F152,U+F185-F186,U+F18E,U+F190-F192,U+F196,U+F1C1-F1C9,U+F1D9,U+F1DB,U+F1E3,U+F1EA,U+F1F7,U+F1F9,U+F20A,U+F247-F248,U+F24A,U+F24D,U+F255-F25B,U+F25D,U+F271-F274,U+F278,U+F27B,U+F28C,U+F28E,U+F29C,U+F2B5,U+F2B7,U+F2BA,U+F2BC,U+F2BE,U+F2C0-F2C1,U+F2C3,U+F2D0,U+F2D2,U+F2D4,U+F2DC; }
@font-face {
font-family: 'FontAwesome';
font-display: block;
src: url("../webfonts/fa-v4compatibility.woff2") format("woff2"), url("../webfonts/fa-v4compatibility.ttf") format("truetype");
unicode-range: U+F041,U+F047,U+F065-F066,U+F07D-F07E,U+F080,U+F08B,U+F08E,U+F090,U+F09A,U+F0AC,U+F0AE,U+F0B2,U+F0D0,U+F0D6,U+F0E4,U+F0EC,U+F10A-F10B,U+F123,U+F13E,U+F148-F149,U+F14C,U+F156,U+F15E,U+F160-F161,U+F163,U+F175-F178,U+F195,U+F1F8,U+F219,U+F27A; }

View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2022 Fonticons, Inc.
*/
@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2"),url(../webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
/*!
* Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2022 Fonticons, Inc.
*/
@font-face {
font-family: 'Font Awesome 5 Brands';
font-display: block;
font-weight: 400;
src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); }
@font-face {
font-family: 'Font Awesome 5 Free';
font-display: block;
font-weight: 900;
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
@font-face {
font-family: 'Font Awesome 5 Free';
font-display: block;
font-weight: 400;
src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); }

View File

@@ -0,0 +1,6 @@
/*!
* Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2022 Fonticons, Inc.
*/
@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/**/*.rs',
],
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/forms'),
],
}