Initial commit squashed
This commit is contained in:
3738
backend/Cargo.lock
generated
Normal file
3738
backend/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
backend/Cargo.toml
Normal file
12
backend/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
[workspace]
|
||||
members = [".", "entity", "migration", "orm", "search", "api"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
booksman-api = { path = "api" }
|
||||
46
backend/api/Cargo.toml
Normal file
46
backend/api/Cargo.toml
Normal file
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "booksman-api"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
[dependencies]
|
||||
booksman-orm = { path = "../orm" }
|
||||
booksman-search = { path = "../search" }
|
||||
migration = { path = "../migration" }
|
||||
entity = { path = "../entity" }
|
||||
axum = "^0.6"
|
||||
axum-extra = { version = "^0.3", features = ["spa"] }
|
||||
clap = { version = "^3", features = ["derive"] }
|
||||
dotenvy = "0.15.0"
|
||||
image = "0.24"
|
||||
error-chain = "0.12.4"
|
||||
log = "^0.4"
|
||||
meilisearch-sdk = "0.20.1"
|
||||
reqwest = {version = "0.11.11", features = ["json"]}
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
serde_json = "^1.0"
|
||||
tokio = { version = "^1", features = ["full"] }
|
||||
tower = "^0.4"
|
||||
tower-http = { version = "^0.3", features = ["full"] }
|
||||
tracing = "^0.1"
|
||||
tracing-subscriber = "^0.3"
|
||||
itertools = "0.10"
|
||||
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]
|
||||
version = "^0.10.6" # sea-orm version
|
||||
features = [
|
||||
"debug-print",
|
||||
"runtime-tokio-native-tls",
|
||||
"sqlx-sqlite",
|
||||
]
|
||||
|
||||
|
||||
1378
backend/api/src/lib.rs
Normal file
1378
backend/api/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
16
backend/entity/Cargo.toml
Normal file
16
backend/entity/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "entity"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "entity"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
chrono = "0.4"
|
||||
|
||||
[dependencies.sea-orm]
|
||||
version = "^0.10.6" # sea-orm version
|
||||
91
backend/entity/src/entities/book.rs
Normal file
91
backend/entity/src/entities/book.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
//! `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 = "book")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub open_library_key: Option<String>,
|
||||
pub title: String,
|
||||
pub edition_count: Option<i32>,
|
||||
pub first_publish_year: Option<i32>,
|
||||
pub median_page_count: Option<i32>,
|
||||
pub goodread_id: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub cover: Option<String>,
|
||||
pub location: Option<String>,
|
||||
pub time_added: Option<String>,
|
||||
pub rating: Option<i32>,
|
||||
pub comments: Option<String>,
|
||||
pub user_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::book_author::Entity")]
|
||||
BookAuthor,
|
||||
#[sea_orm(has_many = "super::book_isbn::Entity")]
|
||||
BookIsbn,
|
||||
#[sea_orm(has_many = "super::book_person::Entity")]
|
||||
BookPerson,
|
||||
#[sea_orm(has_many = "super::book_place::Entity")]
|
||||
BookPlace,
|
||||
#[sea_orm(has_many = "super::book_subject::Entity")]
|
||||
BookSubject,
|
||||
#[sea_orm(has_many = "super::book_time::Entity")]
|
||||
BookTime,
|
||||
#[sea_orm(
|
||||
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 {
|
||||
fn to() -> RelationDef {
|
||||
Relation::BookAuthor.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::book_isbn::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::BookIsbn.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::book_person::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::BookPerson.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::book_place::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::BookPlace.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::book_subject::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::BookSubject.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::book_time::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::BookTime.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::User.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
32
backend/entity/src/entities/book_author.rs
Normal file
32
backend/entity/src/entities/book_author.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! `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 = "book_author")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub author_name: String,
|
||||
pub book_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::book::Entity",
|
||||
from = "Column::BookId",
|
||||
to = "super::book::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Book,
|
||||
}
|
||||
|
||||
impl Related<super::book::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Book.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
32
backend/entity/src/entities/book_isbn.rs
Normal file
32
backend/entity/src/entities/book_isbn.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! `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 = "book_isbn")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub isbn: String,
|
||||
pub book_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::book::Entity",
|
||||
from = "Column::BookId",
|
||||
to = "super::book::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Book,
|
||||
}
|
||||
|
||||
impl Related<super::book::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Book.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
32
backend/entity/src/entities/book_person.rs
Normal file
32
backend/entity/src/entities/book_person.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! `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 = "book_person")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub person: String,
|
||||
pub book_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::book::Entity",
|
||||
from = "Column::BookId",
|
||||
to = "super::book::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Book,
|
||||
}
|
||||
|
||||
impl Related<super::book::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Book.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
32
backend/entity/src/entities/book_place.rs
Normal file
32
backend/entity/src/entities/book_place.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! `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 = "book_place")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub place: String,
|
||||
pub book_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::book::Entity",
|
||||
from = "Column::BookId",
|
||||
to = "super::book::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Book,
|
||||
}
|
||||
|
||||
impl Related<super::book::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Book.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
32
backend/entity/src/entities/book_subject.rs
Normal file
32
backend/entity/src/entities/book_subject.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! `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 = "book_subject")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub subject: String,
|
||||
pub book_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::book::Entity",
|
||||
from = "Column::BookId",
|
||||
to = "super::book::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Book,
|
||||
}
|
||||
|
||||
impl Related<super::book::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Book.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
32
backend/entity/src/entities/book_time.rs
Normal file
32
backend/entity/src/entities/book_time.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! `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 = "book_time")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub time: String,
|
||||
pub book_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::book::Entity",
|
||||
from = "Column::BookId",
|
||||
to = "super::book::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Book,
|
||||
}
|
||||
|
||||
impl Related<super::book::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Book.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
12
backend/entity/src/entities/mod.rs
Normal file
12
backend/entity/src/entities/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub mod book;
|
||||
pub mod book_author;
|
||||
pub mod book_isbn;
|
||||
pub mod book_person;
|
||||
pub mod book_place;
|
||||
pub mod book_subject;
|
||||
pub mod book_time;
|
||||
pub mod user;
|
||||
10
backend/entity/src/entities/prelude.rs
Normal file
10
backend/entity/src/entities/prelude.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6
|
||||
|
||||
pub use super::book::Entity as Book;
|
||||
pub use super::book_author::Entity as BookAuthor;
|
||||
pub use super::book_isbn::Entity as BookIsbn;
|
||||
pub use super::book_person::Entity as BookPerson;
|
||||
pub use super::book_place::Entity as BookPlace;
|
||||
pub use super::book_subject::Entity as BookSubject;
|
||||
pub use super::book_time::Entity as BookTime;
|
||||
pub use super::user::Entity as User;
|
||||
26
backend/entity/src/entities/user.rs
Normal file
26
backend/entity/src/entities/user.rs
Normal 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 {}
|
||||
1
backend/entity/src/lib.rs
Normal file
1
backend/entity/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod entities;
|
||||
23
backend/migration/Cargo.toml
Normal file
23
backend/migration/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "migration"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "migration"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
async-std = { version = "^1", features = ["attributes", "tokio1"] }
|
||||
chrono = "0.4"
|
||||
|
||||
[dependencies.sea-orm-migration]
|
||||
version = "^0.10.6"
|
||||
features = [
|
||||
# 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.
|
||||
# e.g.
|
||||
"runtime-tokio-native-tls", # `ASYNC_RUNTIME` feature
|
||||
"sqlx-sqlite", # `DATABASE_DRIVER` feature
|
||||
]
|
||||
41
backend/migration/README.md
Normal file
41
backend/migration/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Running Migrator CLI
|
||||
|
||||
- Generate a new migration file
|
||||
```sh
|
||||
cargo run -- migrate generate MIGRATION_NAME
|
||||
```
|
||||
- Apply all pending migrations
|
||||
```sh
|
||||
cargo run
|
||||
```
|
||||
```sh
|
||||
cargo run -- up
|
||||
```
|
||||
- Apply first 10 pending migrations
|
||||
```sh
|
||||
cargo run -- up -n 10
|
||||
```
|
||||
- Rollback last applied migrations
|
||||
```sh
|
||||
cargo run -- down
|
||||
```
|
||||
- Rollback last 10 applied migrations
|
||||
```sh
|
||||
cargo run -- down -n 10
|
||||
```
|
||||
- Drop all tables from the database, then reapply all migrations
|
||||
```sh
|
||||
cargo run -- fresh
|
||||
```
|
||||
- Rollback all applied migrations, then reapply all migrations
|
||||
```sh
|
||||
cargo run -- refresh
|
||||
```
|
||||
- Rollback all applied migrations
|
||||
```sh
|
||||
cargo run -- reset
|
||||
```
|
||||
- Check the status of all migrations
|
||||
```sh
|
||||
cargo run -- status
|
||||
```
|
||||
12
backend/migration/src/lib.rs
Normal file
12
backend/migration/src/lib.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
pub use sea_orm_migration::prelude::*;
|
||||
|
||||
mod m20220101_000001_create_table;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![Box::new(m20220101_000001_create_table::Migration)]
|
||||
}
|
||||
}
|
||||
351
backend/migration/src/m20220101_000001_create_table.rs
Normal file
351
backend/migration/src/m20220101_000001_create_table.rs
Normal file
@@ -0,0 +1,351 @@
|
||||
//use chrono::DateTime;
|
||||
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> {
|
||||
// Replace the sample below with your own migration scripts
|
||||
//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
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Book::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Book::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Book::OpenLibraryKey).string())
|
||||
.col(ColumnDef::new(Book::Title).string().not_null())
|
||||
.col(ColumnDef::new(Book::EditionCount).integer())
|
||||
.col(ColumnDef::new(Book::FirstPublishYear).integer())
|
||||
.col(ColumnDef::new(Book::MedianPageCount).integer())
|
||||
.col(ColumnDef::new(Book::GoodreadId).string())
|
||||
.col(ColumnDef::new(Book::Description).string())
|
||||
.col(ColumnDef::new(Book::Cover).string())
|
||||
.col(ColumnDef::new(Book::Location).string())
|
||||
.col(ColumnDef::new(Book::TimeAdded).time())
|
||||
.col(ColumnDef::new(Book::Rating).integer())
|
||||
.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(),
|
||||
)
|
||||
.await
|
||||
.expect("Migration failed");
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(BookAuthor::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(BookAuthor::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(BookAuthor::AuthorName).string().not_null())
|
||||
.col(ColumnDef::new(BookAuthor::BookId).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("FK_2e303c3a712662f1fc2a4d0aad5")
|
||||
.from(BookAuthor::Table, BookAuthor::BookId)
|
||||
.to(Book::Table, Book::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
.expect("Migration failed");
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(BookPerson::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(BookPerson::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(BookPerson::Person).string().not_null())
|
||||
.col(ColumnDef::new(BookPerson::BookId).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("FK_2e303c3a712662f1fc2a4d0aad6")
|
||||
.from(BookPerson::Table, BookPerson::BookId)
|
||||
.to(Book::Table, Book::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
.expect("Migration failed");
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(BookPlace::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(BookPlace::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(BookPlace::Place).string().not_null())
|
||||
.col(ColumnDef::new(BookPlace::BookId).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("FK_2e303c3a712662f1fc2a4d0aad7")
|
||||
.from(BookPlace::Table, BookPlace::BookId)
|
||||
.to(Book::Table, Book::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
.expect("Migration failed");
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(BookSubject::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(BookSubject::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(BookSubject::Subject).string().not_null())
|
||||
.col(ColumnDef::new(BookSubject::BookId).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("FK_2e303c3a712662f1fc2a4d0aad8")
|
||||
.from(BookSubject::Table, BookSubject::BookId)
|
||||
.to(Book::Table, Book::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
.expect("Migration failed");
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(BookTime::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(BookTime::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(BookTime::Time).string().not_null())
|
||||
.col(ColumnDef::new(BookTime::BookId).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("FK_2e303c3a712662f1fc2a4d0aad9")
|
||||
.from(BookTime::Table, BookTime::BookId)
|
||||
.to(Book::Table, Book::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
.expect("Migration failed");
|
||||
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(BookISBN::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(BookISBN::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(BookISBN::ISBN).string().not_null())
|
||||
.col(ColumnDef::new(BookISBN::BookId).integer().not_null())
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("FK_2e303c3a712662f1fc2a4d0aae0")
|
||||
.from(BookISBN::Table, BookISBN::BookId)
|
||||
.to(Book::Table, Book::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// Replace the sample below with your own migration scripts
|
||||
//todo!();
|
||||
|
||||
manager
|
||||
.drop_table(Table::drop().table(Book::Table).to_owned())
|
||||
.await
|
||||
.expect("Drop failed");
|
||||
manager
|
||||
.drop_table(Table::drop().table(BookAuthor::Table).to_owned())
|
||||
.await
|
||||
.expect("Drop failed");
|
||||
manager
|
||||
.drop_table(Table::drop().table(BookPerson::Table).to_owned())
|
||||
.await
|
||||
.expect("Drop failed");
|
||||
manager
|
||||
.drop_table(Table::drop().table(BookPlace::Table).to_owned())
|
||||
.await
|
||||
.expect("Drop failed");
|
||||
manager
|
||||
.drop_table(Table::drop().table(BookSubject::Table).to_owned())
|
||||
.await
|
||||
.expect("Drop failed");
|
||||
manager
|
||||
.drop_table(Table::drop().table(BookTime::Table).to_owned())
|
||||
.await
|
||||
.expect("Drop failed");
|
||||
manager
|
||||
.drop_table(Table::drop().table(BookISBN::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn more at https://docs.rs/sea-query#iden
|
||||
#[derive(Iden)]
|
||||
enum Book {
|
||||
Table,
|
||||
Id,
|
||||
OpenLibraryKey,
|
||||
Title,
|
||||
EditionCount,
|
||||
FirstPublishYear,
|
||||
MedianPageCount,
|
||||
//AuthorName,
|
||||
//Person,
|
||||
//Place,
|
||||
//Subject,
|
||||
//Time,
|
||||
//ISBN,
|
||||
GoodreadId,
|
||||
Description,
|
||||
Cover,
|
||||
Location,
|
||||
TimeAdded,
|
||||
Rating,
|
||||
Comments,
|
||||
UserId,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum BookAuthor {
|
||||
Table,
|
||||
Id,
|
||||
AuthorName,
|
||||
BookId,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum BookPerson {
|
||||
Table,
|
||||
Id,
|
||||
Person,
|
||||
BookId,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum BookPlace {
|
||||
Table,
|
||||
Id,
|
||||
Place,
|
||||
BookId,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum BookSubject {
|
||||
Table,
|
||||
Id,
|
||||
Subject,
|
||||
BookId,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum BookTime {
|
||||
Table,
|
||||
Id,
|
||||
Time,
|
||||
BookId,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum BookISBN {
|
||||
Table,
|
||||
Id,
|
||||
ISBN,
|
||||
BookId,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum User {
|
||||
Table,
|
||||
Id,
|
||||
UserName,
|
||||
PasswordHash,
|
||||
}
|
||||
6
backend/migration/src/main.rs
Normal file
6
backend/migration/src/main.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
cli::run_cli(migration::Migrator).await;
|
||||
}
|
||||
21
backend/orm/Cargo.toml
Normal file
21
backend/orm/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "booksman-orm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
entity = { path = "../entity" }
|
||||
chrono = "0.4"
|
||||
axum-login = "0.4"
|
||||
axum = "^0.6"
|
||||
eyre = "0.6.8"
|
||||
|
||||
[dependencies.sea-orm]
|
||||
version = "^0.10.6" # sea-orm version
|
||||
features = [
|
||||
"debug-print",
|
||||
"runtime-tokio-native-tls",
|
||||
"sqlx-sqlite",
|
||||
]
|
||||
9
backend/orm/src/lib.rs
Normal file
9
backend/orm/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod mutation;
|
||||
mod query;
|
||||
mod userstore;
|
||||
|
||||
pub use mutation::*;
|
||||
pub use query::*;
|
||||
pub use userstore::*;
|
||||
|
||||
pub use sea_orm;
|
||||
223
backend/orm/src/mutation.rs
Normal file
223
backend/orm/src/mutation.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
//use ::entity::entities::prelude::Book;
|
||||
//use ::entity::entities::{prelude::*, *};
|
||||
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};
|
||||
use ::entity::entities::{
|
||||
book, book_author, book_isbn, book_person, book_place, book_subject, book_time, user,
|
||||
};
|
||||
use sea_orm::*;
|
||||
//use ::entity::entities::prelude::Book;
|
||||
|
||||
pub struct Mutation;
|
||||
|
||||
impl Mutation {
|
||||
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,
|
||||
form_data: book::Model,
|
||||
) -> Result<InsertResult<book::ActiveModel>, DbErr> {
|
||||
let record = book::ActiveModel {
|
||||
open_library_key: Set(form_data.open_library_key.to_owned()),
|
||||
title: Set(form_data.title.to_owned()),
|
||||
edition_count: Set(form_data.edition_count.to_owned()),
|
||||
first_publish_year: Set(form_data.first_publish_year.to_owned()),
|
||||
median_page_count: Set(form_data.median_page_count.to_owned()),
|
||||
goodread_id: Set(form_data.goodread_id.to_owned()),
|
||||
description: Set(form_data.description.to_owned()),
|
||||
cover: Set(form_data.cover.to_owned()),
|
||||
location: Set(form_data.location.to_owned()),
|
||||
time_added: Set(form_data.time_added.to_owned()),
|
||||
rating: Set(form_data.rating.to_owned()),
|
||||
comments: Set(form_data.comments.to_owned()),
|
||||
user_id: Set(form_data.user_id.to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
Book::insert(record).exec(db).await
|
||||
}
|
||||
|
||||
pub async fn create_book_author(
|
||||
db: &DbConn,
|
||||
form_data: book_author::Model,
|
||||
) -> Result<book_author::ActiveModel, DbErr> {
|
||||
book_author::ActiveModel {
|
||||
book_id: Set(form_data.book_id.to_owned()),
|
||||
author_name: Set(form_data.author_name.to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
.save(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_book_person(
|
||||
db: &DbConn,
|
||||
form_data: book_person::Model,
|
||||
) -> Result<book_person::ActiveModel, DbErr> {
|
||||
book_person::ActiveModel {
|
||||
book_id: Set(form_data.book_id.to_owned()),
|
||||
person: Set(form_data.person.to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
.save(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_book_place(
|
||||
db: &DbConn,
|
||||
form_data: book_place::Model,
|
||||
) -> Result<book_place::ActiveModel, DbErr> {
|
||||
book_place::ActiveModel {
|
||||
book_id: Set(form_data.book_id.to_owned()),
|
||||
place: Set(form_data.place.to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
.save(db)
|
||||
.await
|
||||
}
|
||||
pub async fn create_book_subject(
|
||||
db: &DbConn,
|
||||
form_data: book_subject::Model,
|
||||
) -> Result<book_subject::ActiveModel, DbErr> {
|
||||
book_subject::ActiveModel {
|
||||
book_id: Set(form_data.book_id.to_owned()),
|
||||
subject: Set(form_data.subject.to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
.save(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_book_time(
|
||||
db: &DbConn,
|
||||
form_data: book_time::Model,
|
||||
) -> Result<book_time::ActiveModel, DbErr> {
|
||||
book_time::ActiveModel {
|
||||
book_id: Set(form_data.book_id.to_owned()),
|
||||
time: Set(form_data.time.to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
.save(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_book_isbn(
|
||||
db: &DbConn,
|
||||
form_data: book_isbn::Model,
|
||||
) -> Result<book_isbn::ActiveModel, DbErr> {
|
||||
book_isbn::ActiveModel {
|
||||
book_id: Set(form_data.book_id.to_owned()),
|
||||
isbn: Set(form_data.isbn.to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
.save(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_book_by_id(
|
||||
db: &DbConn,
|
||||
id: i32,
|
||||
form_data: book::Model,
|
||||
) -> Result<book::Model, DbErr> {
|
||||
let book: book::ActiveModel = Book::find_by_id(id)
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or(DbErr::Custom("Cannot find book.".to_owned()))
|
||||
.map(Into::into)?;
|
||||
|
||||
book::ActiveModel {
|
||||
id: book.id,
|
||||
open_library_key: Set(form_data.open_library_key.to_owned()),
|
||||
title: Set(form_data.title.to_owned()),
|
||||
edition_count: Set(form_data.edition_count.to_owned()),
|
||||
first_publish_year: Set(form_data.first_publish_year.to_owned()),
|
||||
median_page_count: Set(form_data.median_page_count.to_owned()),
|
||||
goodread_id: Set(form_data.goodread_id.to_owned()),
|
||||
description: Set(form_data.description.to_owned()),
|
||||
cover: Set(form_data.cover.to_owned()),
|
||||
location: Set(form_data.location.to_owned()),
|
||||
time_added: Set(form_data.time_added.to_owned()),
|
||||
rating: Set(form_data.rating.to_owned()),
|
||||
comments: Set(form_data.comments.to_owned()),
|
||||
user_id: Set(form_data.user_id.to_owned()),
|
||||
}
|
||||
.update(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_book(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
|
||||
let book: book::ActiveModel = Book::find_by_id(id)
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or(DbErr::Custom("Cannot find book.".to_owned()))
|
||||
.map(Into::into)?;
|
||||
|
||||
book.delete(db).await
|
||||
}
|
||||
|
||||
pub async fn delete_book_author(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
|
||||
book_author::Entity::delete_many()
|
||||
.filter(Condition::any().add(book_author::Column::BookId.eq(id)))
|
||||
.exec(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_book_person(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
|
||||
book_person::Entity::delete_many()
|
||||
.filter(Condition::any().add(book_person::Column::BookId.eq(id)))
|
||||
.exec(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_book_place(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
|
||||
book_place::Entity::delete_many()
|
||||
.filter(Condition::any().add(book_place::Column::BookId.eq(id)))
|
||||
.exec(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_book_subject(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
|
||||
book_subject::Entity::delete_many()
|
||||
.filter(Condition::any().add(book_subject::Column::BookId.eq(id)))
|
||||
.exec(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_book_time(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
|
||||
book_time::Entity::delete_many()
|
||||
.filter(Condition::any().add(book_time::Column::BookId.eq(id)))
|
||||
.exec(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_book_isbn(db: &DbConn, id: i32) -> Result<DeleteResult, DbErr> {
|
||||
book_isbn::Entity::delete_many()
|
||||
.filter(Condition::any().add(book_isbn::Column::BookId.eq(id)))
|
||||
.exec(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_books(db: &DbConn) -> Result<DeleteResult, DbErr> {
|
||||
Book::delete_many().exec(db).await
|
||||
}
|
||||
}
|
||||
131
backend/orm/src/query.rs
Normal file
131
backend/orm/src/query.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
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};
|
||||
use ::entity::entities::{
|
||||
book, book_author, book_isbn, book_person, book_place, book_subject, book_time, user,
|
||||
};
|
||||
//use ::entity::entities::{prelude::*, *};
|
||||
use sea_orm::*;
|
||||
//use sea_query::Expr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BookAndMetaV2 {
|
||||
pub book: book::Model,
|
||||
pub authors: Vec<book_author::Model>,
|
||||
pub persons: Vec<book_person::Model>,
|
||||
pub places: Vec<book_place::Model>,
|
||||
pub subjects: Vec<book_subject::Model>,
|
||||
pub times: Vec<book_time::Model>,
|
||||
pub isbns: Vec<book_isbn::Model>,
|
||||
}
|
||||
//use ::entity::entities::prelude::Book;
|
||||
|
||||
pub struct Query;
|
||||
|
||||
impl Query {
|
||||
pub async fn find_book_by_id(db: &DbConn, id: i32) -> Result<Option<book::Model>, DbErr> {
|
||||
Book::find_by_id(id).one(db).await
|
||||
}
|
||||
|
||||
pub async fn find_userid_by_name(
|
||||
db: &DbConn,
|
||||
name: String,
|
||||
) -> Result<Option<user::Model>, DbErr> {
|
||||
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).
|
||||
pub async fn find_books_in_page(
|
||||
db: &DbConn,
|
||||
page: u64,
|
||||
posts_per_page: u64,
|
||||
userid: i32,
|
||||
sort: String,
|
||||
) -> Result<(Vec<book::Model>, u64), DbErr> {
|
||||
// Setup paginator
|
||||
if sort == "desc".to_string() {
|
||||
let paginator = Book::find()
|
||||
.filter(book::Column::UserId.eq(userid))
|
||||
.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
|
||||
}
|
||||
|
||||
pub async fn find_books_plus_meta_in_page(
|
||||
db: &DbConn,
|
||||
page: usize,
|
||||
posts_per_page: usize,
|
||||
userid: i32,
|
||||
sort: String,
|
||||
) -> Result<(Vec<BookAndMetaV2>, u64), DbErr> {
|
||||
// Setup paginator
|
||||
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();
|
||||
println!("SIZE IS {} and {:?}", book_ids.len(), book_ids);
|
||||
let mut resbooks: Vec<BookAndMetaV2> = Vec::with_capacity(book_ids.len());
|
||||
for book in books.0.iter() {
|
||||
let bauthors: Vec<book_author::Model> =
|
||||
book.find_related(book_author::Entity).all(db).await?;
|
||||
let bpersons: Vec<book_person::Model> =
|
||||
book.find_related(book_person::Entity).all(db).await?;
|
||||
let bplaces: Vec<book_place::Model> =
|
||||
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 {
|
||||
book: (book.clone()),
|
||||
authors: bauthors,
|
||||
persons: bpersons,
|
||||
places: bplaces,
|
||||
subjects: bsubjects,
|
||||
times: btimes,
|
||||
isbns: bisbns,
|
||||
};
|
||||
resbooks.push(bookandmeta);
|
||||
}
|
||||
|
||||
Ok((resbooks, books.1))
|
||||
}
|
||||
}
|
||||
60
backend/orm/src/userstore.rs
Normal file
60
backend/orm/src/userstore.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
11
backend/search/Cargo.toml
Normal file
11
backend/search/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "booksman-search"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
meilisearch-sdk = "0.20.1"
|
||||
tokio = { version = "^1", features = ["full"] }
|
||||
|
||||
61
backend/search/src/lib.rs
Normal file
61
backend/search/src/lib.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use meilisearch_sdk::client::*;
|
||||
use meilisearch_sdk::search::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct BookMeili {
|
||||
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: Vec<String>,
|
||||
pub person: Vec<String>,
|
||||
pub place: Vec<String>,
|
||||
pub subject: Vec<String>,
|
||||
pub time: Vec<String>,
|
||||
pub isbn: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn create_or_update_book(book: BookMeili, userid: i32, client: &Client) {
|
||||
// An index is where the documents are stored.
|
||||
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.
|
||||
books.add_or_replace(&[book], Some("id")).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn delete_book(bookid: i32, userid: i32, client: &Client) {
|
||||
// An index is where the documents are stored.
|
||||
let books = client.index(format!("books{}", userid));
|
||||
books.delete_document(bookid).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn search_book(
|
||||
search: &str,
|
||||
page: usize,
|
||||
userid: i32,
|
||||
client: &Client,
|
||||
) -> Result<(Vec<BookMeili>, usize), meilisearch_sdk::errors::Error> {
|
||||
// An index is where the documents are stored.
|
||||
let books = client.index(format!("books{}", userid));
|
||||
let results: SearchResults<BookMeili> = books
|
||||
.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();
|
||||
//.iter()s.map(|r| r.formatted_result.unwrap()).collect();
|
||||
return Ok((formatted_results, results.estimated_total_hits));
|
||||
}
|
||||
3
backend/src/main.rs
Normal file
3
backend/src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
booksman_api::main();
|
||||
}
|
||||
Reference in New Issue
Block a user