diff --git a/migrations/2022-01-22-200313_create_guilds/up.sql b/migrations/2022-01-22-200313_create_guilds/up.sql index 42afb83..fb17072 100644 --- a/migrations/2022-01-22-200313_create_guilds/up.sql +++ b/migrations/2022-01-22-200313_create_guilds/up.sql @@ -1,6 +1,5 @@ create table if not exists guilds ( guild_id int auto_increment primary key, - external_id varchar(255) not null unique, name varchar(255) not null unique, address text not null, contact_by_account_id int not null, diff --git a/src/actions.rs b/src/actions.rs index 72d76ac..84be313 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -57,7 +57,7 @@ pub fn create_rpg_system( Ok(matching) } -pub fn find_rpg_system(conn: &MysqlConnection, search_id: i32) -> Result { +pub fn find_rpg_system(conn: &MysqlConnection, search_id: Id) -> Result { use crate::schema::rpg_systems::dsl::*; rpg_systems .find(search_id) @@ -67,15 +67,16 @@ pub fn find_rpg_system(conn: &MysqlConnection, search_id: i32) -> Result Result { use crate::schema::rpg_systems::dsl::*; let affected = diesel::update(rpg_systems.find(write_to_id)) - .set(new_info.clone()) + .set(new_info) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!( affected, 1, "update rpg system must affect only a single row." @@ -84,11 +85,12 @@ pub fn update_rpg_system( find_rpg_system(conn, write_to_id) } -pub fn delete_rpgsystem(conn: &MysqlConnection, delete_id: i32) -> Result<(), UE> { +pub fn delete_rpgsystem(conn: &MysqlConnection, delete_id: Id) -> Result<(), UE> { use crate::schema::rpg_systems::dsl::*; let affected = diesel::delete(rpg_systems.find(delete_id)) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!( affected, 1, "delete rpg_system must affect only a single row." @@ -117,14 +119,14 @@ pub fn create_title(conn: &MysqlConnection, new_title: NewTitle) -> Result Result<Title, UE> { +pub fn find_title(conn: &MysqlConnection, search_id: Id) -> Result<Title, UE> { use crate::schema::titles::dsl::*; titles.find(search_id).first(conn).map_err(handle_db_errors) } pub fn update_title( conn: &MysqlConnection, - write_to_id: i32, + write_to_id: Id, new_info: NewTitle, ) -> Result<Title, UE> { use crate::schema::titles::dsl::*; @@ -132,17 +134,19 @@ pub fn update_title( let affected = diesel::update(titles.find(write_to_id)) .set(new_info) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!(affected, 1, "update title must affect only a single row."); find_title(conn, write_to_id) } -pub fn delete_title(conn: &MysqlConnection, delete_id: i32) -> Result<(), UE> { +pub fn delete_title(conn: &MysqlConnection, delete_id: Id) -> Result<(), UE> { use crate::schema::titles::dsl::*; let affected = diesel::delete(titles.find(delete_id)) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!(affected, 1, "delete titles must affect only a single row."); Ok(()) } @@ -157,7 +161,8 @@ pub fn create_account(conn: &MysqlConnection, new_account: NewAccount) -> Result let affected = diesel::insert_into(accounts) .values(new_account.clone()) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!(affected, 1, "crate account must affect only a single row."); let matching = accounts @@ -168,7 +173,7 @@ pub fn create_account(conn: &MysqlConnection, new_account: NewAccount) -> Result Ok(matching) } -pub fn find_account(conn: &MysqlConnection, search_id: i32) -> Result<Account, UE> { +pub fn find_account(conn: &MysqlConnection, search_id: Id) -> Result<Account, UE> { use crate::schema::accounts::dsl::*; accounts .find(search_id) @@ -178,14 +183,15 @@ pub fn find_account(conn: &MysqlConnection, search_id: i32) -> Result<Account, U pub fn update_account( conn: &MysqlConnection, - write_to_id: i32, + write_to_id: Id, new_info: NewAccount, ) -> Result<Account, UE> { use crate::schema::accounts::dsl::*; let affected = diesel::update(accounts.find(write_to_id)) .set(new_info) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!(affected, 1, "update account must affect only a single row."); find_account(conn, write_to_id) @@ -197,7 +203,7 @@ pub fn find_current_registered_account( ) -> Result<Option<Account>, UE> { use crate::schema::accounts::dsl::*; accounts - .filter(external_id.eq(search_external_id).and(active.eq(true))) + .filter(external_id.eq(search_external_id)) .first(conn) .optional() .map_err(handle_db_errors) @@ -221,7 +227,8 @@ pub fn delete_account(conn: &MysqlConnection, account: &Account) -> Result<(), U use crate::schema::accounts::dsl::*; let affected = diesel::delete(accounts.find(account.account_id)) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!( affected, 1, "delete rpg_system must affect only a single row." @@ -243,28 +250,29 @@ pub fn create_guild(conn: &MysqlConnection, new_guild: NewGuild) -> Result<Guild assert_eq!(affected, 1, "create guilds must affect only a single row."); let matching = guilds - .filter(external_id.eq(new_guild.external_id)) + .filter(name.eq(new_guild.name)) .first::<Guild>(conn) .map_err(handle_db_errors)?; Ok(matching) } -pub fn find_guild(conn: &MysqlConnection, search_id: i32) -> Result<Guild, UE> { +pub fn find_guild(conn: &MysqlConnection, search_id: Id) -> Result<Guild, UE> { use crate::schema::guilds::dsl::*; guilds.find(search_id).first(conn).map_err(handle_db_errors) } pub fn update_guild( conn: &MysqlConnection, - write_to_id: i32, + write_to_id: Id, new_info: NewGuild, ) -> Result<Guild, UE> { use crate::schema::guilds::dsl::*; let affected = diesel::update(guilds.find(write_to_id)) .set(new_info) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!(affected, 1, "update guilds must affect only a single row."); find_guild(conn, write_to_id) @@ -299,9 +307,9 @@ pub fn create_book(conn: &MysqlConnection, new_book: NewBook) -> Result<Book, UE /// Little helper function which creates an sql query searching for an inventory key. fn with_inventory_key( - ext_inventory_id: i32, - member_id: Option<i32>, - guild_id: Option<i32>, + ext_inventory_id: Id, + member_id: Option<Id>, + guild_id: Option<Id>, ) -> Box<dyn BoxableExpression<crate::schema::books::table, Mysql, SqlType = Bool>> { use crate::schema::books::dsl::*; let matches_member_id: Box< @@ -328,37 +336,35 @@ fn with_inventory_key( ) } -pub fn find_book(conn: &MysqlConnection, search_id: i32) -> Result<Book, UE> { +pub fn find_book(conn: &MysqlConnection, search_id: Id) -> Result<Book, UE> { use crate::schema::books::dsl::*; books.find(search_id).first(conn).map_err(handle_db_errors) } -pub fn update_book( - conn: &MysqlConnection, - write_to_id: i32, - new_info: NewBook, -) -> Result<Book, UE> { +pub fn update_book(conn: &MysqlConnection, write_to_id: Id, new_info: NewBook) -> Result<Book, UE> { use crate::schema::books::dsl::*; let affected = diesel::update(books.find(write_to_id)) .set(new_info) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!(affected, 1, "update books must affect only a single row."); find_book(conn, write_to_id) } -pub fn delete_book(conn: &MysqlConnection, delete_id: i32) -> Result<(), UE> { +pub fn delete_book(conn: &MysqlConnection, delete_id: Id) -> Result<(), UE> { use crate::schema::books::dsl::*; let affected = diesel::delete(books.find(delete_id)) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!(affected, 1, "delete books must affect only a single row."); Ok(()) } // Member collection -pub fn create_book_owned_by_member( +pub fn create_book_owned_by_account( conn: &MysqlConnection, account: Account, partial_book: PostOwnedBook, @@ -369,10 +375,10 @@ pub fn create_book_owned_by_member( create_book(&conn, new_book) } -pub fn find_book_owned_by_member( +pub fn find_book_owned_by_account( conn: &MysqlConnection, account: Account, - search_id: i32, + search_id: Id, ) -> Result<Book, UE> { use crate::schema::books::dsl::*; books @@ -382,7 +388,7 @@ pub fn find_book_owned_by_member( .map_err(handle_db_errors) } -pub fn list_books_owned_by_member( +pub fn list_books_owned_by_account( conn: &MysqlConnection, account: Account, ) -> Result<Vec<Book>, UE> { @@ -393,10 +399,10 @@ pub fn list_books_owned_by_member( .map_err(handle_db_errors) } -pub fn delete_book_owned_by_member( +pub fn delete_book_owned_by_account( conn: &MysqlConnection, account: &Account, - delete_id: i32, + delete_id: Id, ) -> Result<(), UE> { use crate::schema::books::dsl::*; let affected = diesel::delete( @@ -405,7 +411,8 @@ pub fn delete_book_owned_by_member( .find(delete_id), ) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!(affected, 1, "delete books must affect only a single row."); Ok(()) } @@ -434,7 +441,7 @@ pub fn create_book_owned_by_guild( pub fn find_book_owned_by_guild( conn: &MysqlConnection, guild: &Guild, - search_id: i32, + search_id: Id, ) -> Result<Book, UE> { use crate::schema::books::dsl::*; books @@ -465,7 +472,8 @@ pub fn delete_book_owned_by_guild( .find(delete_id), ) .execute(conn) - .map_err(handle_db_errors)?; + .map_err(handle_db_errors)? + .assert_row_existed()?; assert_eq!(affected, 1, "delete books must affect only a single row."); Ok(()) } @@ -524,3 +532,17 @@ impl AccountAssertions for Option<Account> { self.ok_or(UserFacingError::NotRegistered) } } + +pub trait RowsAffectedAssertions { + fn assert_row_existed(self) -> Result<usize, UE>; +} + +impl RowsAffectedAssertions for usize { + fn assert_row_existed(self) -> Result<usize, UE> { + if self == 0 { + Err(UE::NotFound) + } else { + Ok(self) + } + } +} diff --git a/src/api/me.rs b/src/api/me.rs index a1457a5..6a51f8a 100644 --- a/src/api/me.rs +++ b/src/api/me.rs @@ -75,7 +75,7 @@ pub async fn put( pub mod collection { use crate::actions; - use crate::actions::{AccountAssertions, delete_book_owned_by_member, find_book_owned_by_member}; + use crate::actions::{find_book_owned_by_account, AccountAssertions}; use crate::api::MyResponder; use crate::app::AppState; use crate::authentication::scopes::{COLLECTION_MODIFY, COLLECTION_READ}; @@ -88,9 +88,9 @@ pub mod collection { claims.require_scope(COLLECTION_READ)?; let external_id = claims.external_account_id()?; let conn = app.open_database_connection()?; - let account = actions::find_current_registered_account(&conn, external_id)? - .assert_active()?; - let books = actions::list_books_owned_by_member(&conn, account)?; + let account = + actions::find_current_registered_account(&conn, external_id)?.assert_active()?; + let books = actions::list_books_owned_by_account(&conn, account)?; Ok(HttpResponse::Ok().json(books)) } @@ -102,10 +102,10 @@ pub mod collection { claims.require_scope(COLLECTION_MODIFY)?; let external_id = claims.external_account_id()?; let conn = app.open_database_connection()?; - let account = actions::find_current_registered_account(&conn, external_id)? - .assert_active()?; + let account = + actions::find_current_registered_account(&conn, external_id)?.assert_active()?; let created_book = - actions::create_book_owned_by_member(&conn, account, posted_book.into_inner())?; + actions::create_book_owned_by_account(&conn, account, posted_book.into_inner())?; Ok(HttpResponse::Created().json(created_book)) } @@ -117,9 +117,9 @@ pub mod collection { claims.require_scope(COLLECTION_READ)?; let external_id = claims.external_account_id()?; let conn = app.open_database_connection()?; - let account = actions::find_current_registered_account(&conn, external_id)? - .assert_active()?; - let book = find_book_owned_by_member(&conn, account, *search_id)?; + let account = + actions::find_current_registered_account(&conn, external_id)?.assert_active()?; + let book = find_book_owned_by_account(&conn, account, *search_id)?; Ok(HttpResponse::Created().json(book)) } @@ -131,9 +131,9 @@ pub mod collection { claims.require_scope(COLLECTION_MODIFY)?; let external_id = claims.external_account_id()?; let conn = app.open_database_connection()?; - let account = actions::find_current_registered_account(&conn, external_id)? - .assert_active()?; - delete_book_owned_by_member(&conn, &account, *delete_id)?; + let account = + actions::find_current_registered_account(&conn, external_id)?.assert_active()?; + actions::delete_book_owned_by_account(&conn, &account, *delete_id)?; Ok(HttpResponse::Ok().finish()) } } diff --git a/src/main.rs b/src/main.rs index c9b917b..937ed5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ async fn main() -> Result<(), InternalError> { let matches = command!() .propagate_version(true) .subcommand_required(true) - .arg(arg!(-c --config <CONFIG> "Define the config file")) + .arg(arg!(-c --config <CONFIG> "set the config file")) .subcommand(Command::new("serve").about("start the liberation service")) .subcommand(Command::new("test").about("run whatever was programed")) .get_matches(); diff --git a/src/models.rs b/src/models.rs index d60310d..4b3bef9 100644 --- a/src/models.rs +++ b/src/models.rs @@ -17,7 +17,7 @@ pub type Id = i32; #[table_name = "rpg_systems"] #[primary_key(rpg_system_id)] pub struct RpgSystem { - pub rpg_system_id: i32, + pub rpg_system_id: Id, pub name: String, pub shortname: Option<String>, } @@ -36,9 +36,9 @@ pub struct NewRpgSystem { #[primary_key(title_id)] #[belongs_to(RpgSystem, foreign_key = "rpg_system_by_id")] pub struct Title { - pub title_id: i32, + pub title_id: Id, pub name: String, - pub rpg_system_by_id: i32, + pub rpg_system_by_id: Id, pub language: String, pub publisher: String, pub year: Year, @@ -49,7 +49,7 @@ pub struct Title { #[table_name = "titles"] pub struct NewTitle { pub name: String, - pub rpg_system_by_id: i32, + pub rpg_system_by_id: Id, pub language: String, pub publisher: String, pub year: Year, @@ -60,7 +60,7 @@ pub struct NewTitle { #[table_name = "accounts"] #[primary_key(account_id)] pub struct Account { - pub account_id: i32, + pub account_id: Id, pub active: bool, pub external_id: String, pub full_name: String, @@ -72,7 +72,7 @@ pub struct Account { /// DTO with less information about a user #[derive(Serialize, Debug)] pub struct User { - pub id: i32, + pub id: Id, pub active: bool, pub full_name: String, } @@ -107,33 +107,31 @@ pub struct AccountActive { #[table_name = "guilds"] #[primary_key(guild_id)] pub struct Guild { - pub guild_id: i32, - pub external_id: String, + pub guild_id: Id, pub name: String, pub address: String, - pub contact_by_account_id: i32, + pub contact_by_account_id: Id, } #[derive(Insertable, AsChangeset, Deserialize, Clone)] #[table_name = "guilds"] pub struct NewGuild { - pub external_id: String, pub name: String, pub address: String, - pub contact_by_account_id: i32, + pub contact_by_account_id: Id, } #[derive(Deserialize, Serialize, Copy, Clone, Debug)] #[serde(tag = "type")] pub enum Owner { #[serde(rename = "member")] - Member { id: i32 }, + Member { id: Id }, #[serde(rename = "guild")] - Guild { id: i32 }, + Guild { id: Id }, } -impl From<(Option<i32>, Option<i32>)> for Owner { - fn from(ids: (Option<i32>, Option<i32>)) -> Self { +impl From<(Option<Id>, Option<Id>)> for Owner { + fn from(ids: (Option<Id>, Option<Id>)) -> Self { match ids { (Some(id), None) => Self::Member { id }, (None, Some(id)) => Self::Guild { id }, @@ -142,8 +140,8 @@ impl From<(Option<i32>, Option<i32>)> for Owner { } } -impl Into<(Option<i32>, Option<i32>)> for Owner { - fn into(self) -> (Option<i32>, Option<i32>) { +impl Into<(Option<Id>, Option<Id>)> for Owner { + fn into(self) -> (Option<Id>, Option<Id>) { match self { Self::Member { id } => (Some(id), None), Self::Guild { id } => (None, Some(id)), @@ -154,8 +152,8 @@ impl Into<(Option<i32>, Option<i32>)> for Owner { // This maps the owner to the correct colums. `cargo expand` does really help here. impl Insertable<books::table> for Owner { type Values = <( - Option<diesel::dsl::Eq<books::owner_member_by_id, i32>>, - Option<diesel::dsl::Eq<books::owner_guild_by_id, i32>>, + Option<diesel::dsl::Eq<books::owner_member_by_id, Id>>, + Option<diesel::dsl::Eq<books::owner_guild_by_id, Id>>, ) as Insertable<books::table>>::Values; fn values(self) -> Self::Values { @@ -172,8 +170,8 @@ impl Insertable<books::table> for Owner { // Borrowed variant, which is also needed impl<'insert> Insertable<books::table> for &'insert Owner { type Values = <( - Option<diesel::dsl::Eq<books::owner_member_by_id, i32>>, - Option<diesel::dsl::Eq<books::owner_guild_by_id, i32>>, + Option<diesel::dsl::Eq<books::owner_member_by_id, Id>>, + Option<diesel::dsl::Eq<books::owner_guild_by_id, Id>>, ) as Insertable<books::table>>::Values; fn values(self) -> Self::Values { @@ -195,15 +193,15 @@ impl<'insert> Insertable<books::table> for &'insert Owner { #[belongs_to(Account, foreign_key=owner_member_by_id)] //#[belongs_to(Guild, foreign_key=owner_guild_by_id)] pub struct Book { - pub book_id: i32, - pub title_by_id: i32, + pub book_id: Id, + pub title_by_id: Id, pub owner: Owner, pub quality: String, - pub external_inventory_id: i32, + pub external_inventory_id: Id, } impl Queryable<books::SqlType, Mysql> for Book { - type Row = (i32, i32, Option<i32>, Option<i32>, String, i32); + type Row = (Id, Id, Option<Id>, Option<Id>, String, Id); fn build(row: Self::Row) -> Self { Book { @@ -219,20 +217,20 @@ impl Queryable<books::SqlType, Mysql> for Book { #[derive(Insertable, Deserialize, Clone)] #[table_name = "books"] pub struct NewBook { - pub title_by_id: i32, + pub title_by_id: Id, #[diesel(embed)] pub owner: Owner, // rentee: MemberOrGuild, pub quality: String, - pub external_inventory_id: i32, + pub external_inventory_id: Id, } /// Allows creation of books, where the owner is derived from the token or endpoint. #[derive(Deserialize, Clone)] pub struct PostOwnedBook { - pub title_by_id: i32, + pub title_by_id: Id, pub quality: String, - pub external_inventory_id: i32, + pub external_inventory_id: Id, } impl PostOwnedBook { @@ -249,11 +247,11 @@ impl PostOwnedBook { impl AsChangeset for NewBook { type Target = books::table; type Changeset = <( - diesel::dsl::Eq<books::title_by_id, i32>, - diesel::dsl::Eq<books::owner_member_by_id, Option<i32>>, - diesel::dsl::Eq<books::owner_guild_by_id, Option<i32>>, + diesel::dsl::Eq<books::title_by_id, Id>, + diesel::dsl::Eq<books::owner_member_by_id, Option<Id>>, + diesel::dsl::Eq<books::owner_guild_by_id, Option<Id>>, diesel::dsl::Eq<books::quality, String>, - diesel::dsl::Eq<books::external_inventory_id, i32>, + diesel::dsl::Eq<books::external_inventory_id, Id>, ) as AsChangeset>::Changeset; fn as_changeset(self) -> Self::Changeset { @@ -273,13 +271,13 @@ impl AsChangeset for NewBook { #[derive(Insertable, Deserialize, Clone)] #[table_name = "librarians"] pub struct NewLibrarian { - account_id: i32, - guild_id: i32, + account_id: Id, + guild_id: Id, } #[derive(Queryable, Clone)] pub struct Librarian { - _permission_id: i32, - pub account_id: i32, - pub guild_id: i32, + _permission_id: Id, + pub account_id: Id, + pub guild_id: Id, } diff --git a/src/schema.rs b/src/schema.rs index 55a1e63..9af3273 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -24,7 +24,6 @@ table! { table! { guilds (guild_id) { guild_id -> Integer, - external_id -> Varchar, name -> Varchar, address -> Text, contact_by_account_id -> Integer, @@ -67,4 +66,11 @@ joinable!(librarians -> accounts (account_id)); joinable!(librarians -> guilds (guild_id)); joinable!(titles -> rpg_systems (rpg_system_by_id)); -allow_tables_to_appear_in_same_query!(accounts, books, guilds, librarians, rpg_systems, titles,); +allow_tables_to_appear_in_same_query!( + accounts, + books, + guilds, + librarians, + rpg_systems, + titles, +);