Create user repository
This commit is contained in:
2096
Cargo.lock
generated
Normal file
2096
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,7 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
members = []
|
members = [
|
||||||
|
"rotom_core",
|
||||||
|
"rotom_database",
|
||||||
|
]
|
||||||
|
|||||||
7
rotom_core/Cargo.toml
Normal file
7
rotom_core/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "rotom_core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait = "0.1.85"
|
||||||
1
rotom_core/src/lib.rs
Normal file
1
rotom_core/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod repository;
|
||||||
21
rotom_core/src/repository/mod.rs
Normal file
21
rotom_core/src/repository/mod.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use user_repository::UserRepository;
|
||||||
|
|
||||||
|
pub mod user_repository;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait RepositoryProvider {
|
||||||
|
type BackendError: std::error::Error;
|
||||||
|
|
||||||
|
type Repository<'a>: Repository<BackendError = <Self as RepositoryProvider>::BackendError> + Send + Sync
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
async fn get(&self) -> Result<Self::Repository<'_>, Self::BackendError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Repository
|
||||||
|
where
|
||||||
|
Self: UserRepository<BackendError = <Self as Repository>::BackendError>,
|
||||||
|
{
|
||||||
|
type BackendError: std::error::Error;
|
||||||
|
}
|
||||||
27
rotom_core/src/repository/user_repository.rs
Normal file
27
rotom_core/src/repository/user_repository.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait UserRepository {
|
||||||
|
type BackendError: std::error::Error;
|
||||||
|
|
||||||
|
async fn user(&mut self, id: i32) -> Result<Option<User>, Self::BackendError>;
|
||||||
|
|
||||||
|
async fn insert_user(&mut self, new_user: NewUser) -> Result<User, Self::BackendError>;
|
||||||
|
|
||||||
|
async fn update_user(&mut self, user: User) -> Result<Option<User>, Self::BackendError>;
|
||||||
|
|
||||||
|
async fn remove_user(&mut self, id: i32) -> Result<Option<User>, Self::BackendError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct User {
|
||||||
|
pub id: i32,
|
||||||
|
pub discord_user_id: u64,
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NewUser {
|
||||||
|
pub discord_user_id: u64,
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-trait = "0.1.85"
|
||||||
diesel = { version = "2.2.6", default_features = false }
|
diesel = { version = "2.2.6", default_features = false }
|
||||||
diesel-async = { version = "0.5.2", features = ["bb8"] }
|
diesel-async = { version = "0.5.2", features = ["bb8"] }
|
||||||
diesel_migrations = "2.2.0"
|
diesel_migrations = "2.2.0"
|
||||||
|
rotom_core = { path = "../rotom_core" }
|
||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ pub enum BackendError {
|
|||||||
DieselQueryError(#[from] diesel::result::Error),
|
DieselQueryError(#[from] diesel::result::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
DieselMigrationError(Box<dyn std::error::Error + Send + Sync>),
|
DieselMigrationError(Box<dyn std::error::Error + Send + Sync>),
|
||||||
|
#[error(transparent)]
|
||||||
|
Bb8RunError(#[from] diesel_async::pooled_connection::bb8::RunError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<diesel_async::pooled_connection::PoolError> for BackendError {
|
impl From<diesel_async::pooled_connection::PoolError> for BackendError {
|
||||||
|
|||||||
@@ -2,28 +2,28 @@ use diesel::Connection;
|
|||||||
use diesel::MysqlConnection;
|
use diesel::MysqlConnection;
|
||||||
use diesel_async::pooled_connection::bb8::Pool;
|
use diesel_async::pooled_connection::bb8::Pool;
|
||||||
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
||||||
use diesel_async::AsyncMysqlConnection;
|
|
||||||
use diesel_migrations::embed_migrations;
|
use diesel_migrations::embed_migrations;
|
||||||
use diesel_migrations::EmbeddedMigrations;
|
use diesel_migrations::EmbeddedMigrations;
|
||||||
use diesel_migrations::MigrationHarness;
|
use diesel_migrations::MigrationHarness;
|
||||||
|
use repository::MysqlRepositoryProvider;
|
||||||
|
|
||||||
use crate::BackendError;
|
use crate::BackendError;
|
||||||
|
|
||||||
|
pub mod repository;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
|
||||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/mysql");
|
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/mysql");
|
||||||
|
|
||||||
pub fn run_pending_migrations(database_url: &str) -> Result<(), BackendError> {
|
pub fn run_pending_migrations(database_url: &str) -> Result<(), BackendError> {
|
||||||
let mut connection = MysqlConnection::establish(database_url)?;
|
let mut connection = MysqlConnection::establish(database_url)?;
|
||||||
connection.run_pending_migrations(MIGRATIONS)
|
connection
|
||||||
|
.run_pending_migrations(MIGRATIONS)
|
||||||
.map_err(BackendError::DieselMigrationError)?;
|
.map_err(BackendError::DieselMigrationError)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn establish_async_pool(
|
pub async fn repository_provider(database_url: &str) -> Result<MysqlRepositoryProvider, BackendError> {
|
||||||
database_url: &str,
|
|
||||||
) -> Result<Pool<AsyncMysqlConnection>, BackendError> {
|
|
||||||
let config = AsyncDieselConnectionManager::new(database_url);
|
let config = AsyncDieselConnectionManager::new(database_url);
|
||||||
let pool = Pool::builder().build(config).await?;
|
let pool = Pool::builder().build(config).await?;
|
||||||
Ok(pool)
|
Ok(MysqlRepositoryProvider::new(pool))
|
||||||
}
|
}
|
||||||
|
|||||||
44
rotom_database/src/mysql/repository/mod.rs
Normal file
44
rotom_database/src/mysql/repository/mod.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use diesel_async::pooled_connection::bb8::Pool;
|
||||||
|
use diesel_async::pooled_connection::bb8::PooledConnection;
|
||||||
|
use diesel_async::AsyncMysqlConnection;
|
||||||
|
use rotom_core::repository::Repository;
|
||||||
|
use rotom_core::repository::RepositoryProvider;
|
||||||
|
|
||||||
|
use crate::BackendError;
|
||||||
|
|
||||||
|
mod user_repository;
|
||||||
|
|
||||||
|
pub struct MysqlRepository<'a> {
|
||||||
|
conn: PooledConnection<'a, AsyncMysqlConnection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MysqlRepository<'a> {
|
||||||
|
pub fn new(conn: PooledConnection<'a, AsyncMysqlConnection>) -> Self {
|
||||||
|
Self { conn }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repository for MysqlRepository<'_> {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MysqlRepositoryProvider {
|
||||||
|
pool: Pool<AsyncMysqlConnection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MysqlRepositoryProvider {
|
||||||
|
pub fn new(pool: Pool<AsyncMysqlConnection>) -> Self {
|
||||||
|
Self { pool }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl RepositoryProvider for MysqlRepositoryProvider {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
|
||||||
|
type Repository<'a> = MysqlRepository<'a>;
|
||||||
|
|
||||||
|
async fn get(&self) -> Result<Self::Repository<'_>, Self::BackendError> {
|
||||||
|
self.pool.get().await.map(MysqlRepository::new).map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
160
rotom_database/src/mysql/repository/user_repository.rs
Normal file
160
rotom_database/src/mysql/repository/user_repository.rs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::scoped_futures::ScopedFutureExt;
|
||||||
|
use diesel_async::AsyncConnection;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use rotom_core::repository::user_repository::NewUser;
|
||||||
|
use rotom_core::repository::user_repository::User;
|
||||||
|
use rotom_core::repository::user_repository::UserRepository;
|
||||||
|
|
||||||
|
use crate::mysql::schema::users;
|
||||||
|
use crate::BackendError;
|
||||||
|
|
||||||
|
use super::MysqlRepository;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
impl UserRepository for MysqlRepository<'_> {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
|
||||||
|
async fn user(&mut self, id: i32) -> Result<Option<User>, Self::BackendError> {
|
||||||
|
users::dsl::users.find(id)
|
||||||
|
.first::<ModelUser>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.optional()
|
||||||
|
.map(|option| option.map(User::from))
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_user(&mut self, new_user: NewUser) -> Result<User, Self::BackendError> {
|
||||||
|
let model_new_user = ModelNewUser::from(new_user);
|
||||||
|
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(|conn| async move {
|
||||||
|
diesel::insert_into(users::table)
|
||||||
|
.values(&model_new_user)
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
users::table
|
||||||
|
.order(users::id.desc())
|
||||||
|
.select(ModelUser::as_select())
|
||||||
|
.first(conn)
|
||||||
|
.await
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(User::from)
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_user(&mut self, user: User) -> Result<Option<User>, Self::BackendError> {
|
||||||
|
let model_user = ModelUser::from(user);
|
||||||
|
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(|conn| async move {
|
||||||
|
let option_previous = users::dsl::users.find(model_user.id)
|
||||||
|
.select(ModelUser::as_select())
|
||||||
|
.first(conn)
|
||||||
|
.await
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
let previous = match option_previous {
|
||||||
|
Some(previous) => previous,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::update(users::dsl::users.find(model_user.id))
|
||||||
|
.set(&model_user)
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(previous))
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(|option| option.map(User::from))
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_user(&mut self, id: i32) -> Result<Option<User>, Self::BackendError> {
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(move |conn| async move {
|
||||||
|
let option_removed = users::dsl::users.find(id)
|
||||||
|
.select(ModelUser::as_select())
|
||||||
|
.first(conn)
|
||||||
|
.await
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
let removed = match option_removed {
|
||||||
|
Some(previous) => previous,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::delete(users::dsl::users.find(id))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(removed))
|
||||||
|
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(|option| option.map(User::from))
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, AsChangeset)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||||
|
struct ModelUser {
|
||||||
|
id: i32,
|
||||||
|
discord_user_id: i64,
|
||||||
|
pokemon_go_code: Option<String>,
|
||||||
|
pokemon_pocket_code: Option<String>,
|
||||||
|
switch_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModelUser> for User {
|
||||||
|
fn from(value: ModelUser) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
discord_user_id: value.discord_user_id as u64,
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<User> for ModelUser {
|
||||||
|
fn from(value: User) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
discord_user_id: value.discord_user_id as i64,
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||||
|
struct ModelNewUser {
|
||||||
|
discord_user_id: i64,
|
||||||
|
pokemon_go_code: Option<String>,
|
||||||
|
pokemon_pocket_code: Option<String>,
|
||||||
|
switch_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NewUser> for ModelNewUser {
|
||||||
|
fn from(value: NewUser) -> Self {
|
||||||
|
Self {
|
||||||
|
discord_user_id: value.discord_user_id as i64,
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,28 +2,28 @@ use diesel::Connection;
|
|||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use diesel_async::pooled_connection::bb8::Pool;
|
use diesel_async::pooled_connection::bb8::Pool;
|
||||||
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
||||||
use diesel_async::AsyncPgConnection;
|
|
||||||
use diesel_migrations::embed_migrations;
|
use diesel_migrations::embed_migrations;
|
||||||
use diesel_migrations::EmbeddedMigrations;
|
use diesel_migrations::EmbeddedMigrations;
|
||||||
use diesel_migrations::MigrationHarness;
|
use diesel_migrations::MigrationHarness;
|
||||||
|
use repository::PostgresRepositoryProvider;
|
||||||
|
|
||||||
use crate::BackendError;
|
use crate::BackendError;
|
||||||
|
|
||||||
|
pub mod repository;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
|
||||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/postgres");
|
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/postgres");
|
||||||
|
|
||||||
pub fn run_pending_migrations(database_url: &str) -> Result<(), BackendError> {
|
pub fn run_pending_migrations(database_url: &str) -> Result<(), BackendError> {
|
||||||
let mut connection = PgConnection::establish(database_url)?;
|
let mut connection = PgConnection::establish(database_url)?;
|
||||||
connection.run_pending_migrations(MIGRATIONS)
|
connection
|
||||||
|
.run_pending_migrations(MIGRATIONS)
|
||||||
.map_err(BackendError::DieselMigrationError)?;
|
.map_err(BackendError::DieselMigrationError)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn establish_async_pool(
|
pub async fn repository_provider(database_url: &str) -> Result<PostgresRepositoryProvider, BackendError> {
|
||||||
database_url: &str,
|
|
||||||
) -> Result<Pool<AsyncPgConnection>, BackendError> {
|
|
||||||
let config = AsyncDieselConnectionManager::new(database_url);
|
let config = AsyncDieselConnectionManager::new(database_url);
|
||||||
let pool = Pool::builder().build(config).await?;
|
let pool = Pool::builder().build(config).await?;
|
||||||
Ok(pool)
|
Ok(PostgresRepositoryProvider::new(pool))
|
||||||
}
|
}
|
||||||
|
|||||||
44
rotom_database/src/postgres/repository/mod.rs
Normal file
44
rotom_database/src/postgres/repository/mod.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use diesel_async::pooled_connection::bb8::Pool;
|
||||||
|
use diesel_async::pooled_connection::bb8::PooledConnection;
|
||||||
|
use diesel_async::AsyncPgConnection;
|
||||||
|
use rotom_core::repository::Repository;
|
||||||
|
use rotom_core::repository::RepositoryProvider;
|
||||||
|
|
||||||
|
use crate::BackendError;
|
||||||
|
|
||||||
|
mod user_repository;
|
||||||
|
|
||||||
|
pub struct PostgresRepository<'a> {
|
||||||
|
conn: PooledConnection<'a, AsyncPgConnection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PostgresRepository<'a> {
|
||||||
|
pub fn new(conn: PooledConnection<'a, AsyncPgConnection>) -> Self {
|
||||||
|
Self { conn }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repository for PostgresRepository<'_> {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PostgresRepositoryProvider {
|
||||||
|
pool: Pool<AsyncPgConnection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostgresRepositoryProvider {
|
||||||
|
pub fn new(pool: Pool<AsyncPgConnection>) -> Self {
|
||||||
|
Self { pool }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl RepositoryProvider for PostgresRepositoryProvider {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
|
||||||
|
type Repository<'a> = PostgresRepository<'a>;
|
||||||
|
|
||||||
|
async fn get(&self) -> Result<Self::Repository<'_>, Self::BackendError> {
|
||||||
|
self.pool.get().await.map(PostgresRepository::new).map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
143
rotom_database/src/postgres/repository/user_repository.rs
Normal file
143
rotom_database/src/postgres/repository/user_repository.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::scoped_futures::ScopedFutureExt;
|
||||||
|
use diesel_async::AsyncConnection;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use rotom_core::repository::user_repository::NewUser;
|
||||||
|
use rotom_core::repository::user_repository::User;
|
||||||
|
use rotom_core::repository::user_repository::UserRepository;
|
||||||
|
|
||||||
|
use crate::postgres::schema::users;
|
||||||
|
use crate::BackendError;
|
||||||
|
|
||||||
|
use super::PostgresRepository;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
impl UserRepository for PostgresRepository<'_> {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
|
||||||
|
async fn user(&mut self, id: i32) -> Result<Option<User>, Self::BackendError> {
|
||||||
|
users::dsl::users.find(id)
|
||||||
|
.first::<ModelUser>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.optional()
|
||||||
|
.map(|option| option.map(User::from))
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_user(&mut self, new_user: NewUser) -> Result<User, Self::BackendError> {
|
||||||
|
let model_new_user = ModelNewUser::from(new_user);
|
||||||
|
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(|conn| async move {
|
||||||
|
diesel::insert_into(users::table)
|
||||||
|
.values(&model_new_user)
|
||||||
|
.returning(ModelUser::as_returning())
|
||||||
|
.get_result(conn)
|
||||||
|
.await
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(User::from)
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_user(&mut self, user: User) -> Result<Option<User>, Self::BackendError> {
|
||||||
|
let model_user = ModelUser::from(user);
|
||||||
|
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(|conn| async move {
|
||||||
|
let option_previous = users::dsl::users.find(model_user.id)
|
||||||
|
.select(ModelUser::as_select())
|
||||||
|
.first(conn)
|
||||||
|
.await
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
let previous = match option_previous {
|
||||||
|
Some(previous) => previous,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::update(users::dsl::users.find(model_user.id))
|
||||||
|
.set(&model_user)
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(previous))
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(|option| option.map(User::from))
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_user(&mut self, id: i32) -> Result<Option<User>, Self::BackendError> {
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(move |conn| async move {
|
||||||
|
diesel::delete(users::dsl::users.find(id))
|
||||||
|
.returning(ModelUser::as_returning())
|
||||||
|
.get_result(conn)
|
||||||
|
.await
|
||||||
|
.optional()
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(|option| option.map(User::from))
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, AsChangeset)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
struct ModelUser {
|
||||||
|
id: i32,
|
||||||
|
discord_user_id: i64,
|
||||||
|
pokemon_go_code: Option<String>,
|
||||||
|
pokemon_pocket_code: Option<String>,
|
||||||
|
switch_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModelUser> for User {
|
||||||
|
fn from(value: ModelUser) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
discord_user_id: value.discord_user_id as u64,
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<User> for ModelUser {
|
||||||
|
fn from(value: User) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
discord_user_id: value.discord_user_id as i64,
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
struct ModelNewUser {
|
||||||
|
discord_user_id: i64,
|
||||||
|
pokemon_go_code: Option<String>,
|
||||||
|
pokemon_pocket_code: Option<String>,
|
||||||
|
switch_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NewUser> for ModelNewUser {
|
||||||
|
fn from(value: NewUser) -> Self {
|
||||||
|
Self {
|
||||||
|
discord_user_id: value.discord_user_id as i64,
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,28 +2,28 @@ use diesel::Connection;
|
|||||||
use diesel::SqliteConnection;
|
use diesel::SqliteConnection;
|
||||||
use diesel_async::pooled_connection::bb8::Pool;
|
use diesel_async::pooled_connection::bb8::Pool;
|
||||||
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
||||||
use diesel_async::sync_connection_wrapper::SyncConnectionWrapper;
|
|
||||||
use diesel_migrations::embed_migrations;
|
use diesel_migrations::embed_migrations;
|
||||||
use diesel_migrations::EmbeddedMigrations;
|
use diesel_migrations::EmbeddedMigrations;
|
||||||
use diesel_migrations::MigrationHarness;
|
use diesel_migrations::MigrationHarness;
|
||||||
|
use repository::SqliteRepositoryProvider;
|
||||||
|
|
||||||
use crate::BackendError;
|
use crate::BackendError;
|
||||||
|
|
||||||
|
pub mod repository;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
|
||||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/sqlite");
|
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/sqlite");
|
||||||
|
|
||||||
pub fn run_pending_migrations(database_url: &str) -> Result<(), BackendError> {
|
pub fn run_pending_migrations(database_url: &str) -> Result<(), BackendError> {
|
||||||
let mut connection = SqliteConnection::establish(database_url)?;
|
let mut connection = SqliteConnection::establish(database_url)?;
|
||||||
connection.run_pending_migrations(MIGRATIONS)
|
connection
|
||||||
|
.run_pending_migrations(MIGRATIONS)
|
||||||
.map_err(BackendError::DieselMigrationError)?;
|
.map_err(BackendError::DieselMigrationError)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn establish_async_pool(
|
pub async fn repository_provider(database_url: &str) -> Result<SqliteRepositoryProvider, BackendError> {
|
||||||
database_url: &str,
|
|
||||||
) -> Result<Pool<SyncConnectionWrapper<SqliteConnection>>, BackendError> {
|
|
||||||
let config = AsyncDieselConnectionManager::new(database_url);
|
let config = AsyncDieselConnectionManager::new(database_url);
|
||||||
let pool = Pool::builder().build(config).await?;
|
let pool = Pool::builder().build(config).await?;
|
||||||
Ok(pool)
|
Ok(SqliteRepositoryProvider::new(pool))
|
||||||
}
|
}
|
||||||
|
|||||||
45
rotom_database/src/sqlite/repository/mod.rs
Normal file
45
rotom_database/src/sqlite/repository/mod.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use diesel::SqliteConnection;
|
||||||
|
use diesel_async::pooled_connection::bb8::Pool;
|
||||||
|
use diesel_async::pooled_connection::bb8::PooledConnection;
|
||||||
|
use diesel_async::sync_connection_wrapper::SyncConnectionWrapper;
|
||||||
|
use rotom_core::repository::Repository;
|
||||||
|
use rotom_core::repository::RepositoryProvider;
|
||||||
|
|
||||||
|
use crate::BackendError;
|
||||||
|
|
||||||
|
mod user_repository;
|
||||||
|
|
||||||
|
pub struct SqliteRepository<'a> {
|
||||||
|
conn: PooledConnection<'a, SyncConnectionWrapper<SqliteConnection>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SqliteRepository<'a> {
|
||||||
|
pub fn new(conn: PooledConnection<'a, SyncConnectionWrapper<SqliteConnection>>) -> Self {
|
||||||
|
Self { conn }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repository for SqliteRepository<'_> {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SqliteRepositoryProvider {
|
||||||
|
pool: Pool<SyncConnectionWrapper<SqliteConnection>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SqliteRepositoryProvider {
|
||||||
|
pub fn new(pool: Pool<SyncConnectionWrapper<SqliteConnection>>) -> Self {
|
||||||
|
Self { pool }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl RepositoryProvider for SqliteRepositoryProvider {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
|
||||||
|
type Repository<'a> = SqliteRepository<'a>;
|
||||||
|
|
||||||
|
async fn get(&self) -> Result<Self::Repository<'_>, Self::BackendError> {
|
||||||
|
self.pool.get().await.map(SqliteRepository::new).map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
143
rotom_database/src/sqlite/repository/user_repository.rs
Normal file
143
rotom_database/src/sqlite/repository/user_repository.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::scoped_futures::ScopedFutureExt;
|
||||||
|
use diesel_async::AsyncConnection;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use rotom_core::repository::user_repository::NewUser;
|
||||||
|
use rotom_core::repository::user_repository::User;
|
||||||
|
use rotom_core::repository::user_repository::UserRepository;
|
||||||
|
|
||||||
|
use crate::sqlite::schema::users;
|
||||||
|
use crate::BackendError;
|
||||||
|
|
||||||
|
use super::SqliteRepository;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
impl UserRepository for SqliteRepository<'_> {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
|
||||||
|
async fn user(&mut self, id: i32) -> Result<Option<User>, Self::BackendError> {
|
||||||
|
users::dsl::users.find(id)
|
||||||
|
.first::<ModelUser>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.optional()
|
||||||
|
.map(|option| option.map(User::from))
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insert_user(&mut self, new_user: NewUser) -> Result<User, Self::BackendError> {
|
||||||
|
let model_new_user = ModelNewUser::from(new_user);
|
||||||
|
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(|conn| async move {
|
||||||
|
diesel::insert_into(users::table)
|
||||||
|
.values(&model_new_user)
|
||||||
|
.returning(ModelUser::as_returning())
|
||||||
|
.get_result(conn)
|
||||||
|
.await
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(User::from)
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_user(&mut self, user: User) -> Result<Option<User>, Self::BackendError> {
|
||||||
|
let model_user = ModelUser::from(user);
|
||||||
|
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(|conn| async move {
|
||||||
|
let option_previous = users::dsl::users.find(model_user.id)
|
||||||
|
.select(ModelUser::as_select())
|
||||||
|
.first(conn)
|
||||||
|
.await
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
let previous = match option_previous {
|
||||||
|
Some(previous) => previous,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
diesel::update(users::dsl::users.find(model_user.id))
|
||||||
|
.set(&model_user)
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(previous))
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(|option| option.map(User::from))
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_user(&mut self, id: i32) -> Result<Option<User>, Self::BackendError> {
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(move |conn| async move {
|
||||||
|
diesel::delete(users::dsl::users.find(id))
|
||||||
|
.returning(ModelUser::as_returning())
|
||||||
|
.get_result(conn)
|
||||||
|
.await
|
||||||
|
.optional()
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(|option| option.map(User::from))
|
||||||
|
.map_err(BackendError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, AsChangeset)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||||
|
struct ModelUser {
|
||||||
|
id: i32,
|
||||||
|
discord_user_id: i64,
|
||||||
|
pokemon_go_code: Option<String>,
|
||||||
|
pokemon_pocket_code: Option<String>,
|
||||||
|
switch_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModelUser> for User {
|
||||||
|
fn from(value: ModelUser) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
discord_user_id: value.discord_user_id as u64,
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<User> for ModelUser {
|
||||||
|
fn from(value: User) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
discord_user_id: value.discord_user_id as i64,
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||||
|
struct ModelNewUser {
|
||||||
|
discord_user_id: i64,
|
||||||
|
pokemon_go_code: Option<String>,
|
||||||
|
pokemon_pocket_code: Option<String>,
|
||||||
|
switch_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NewUser> for ModelNewUser {
|
||||||
|
fn from(value: NewUser) -> Self {
|
||||||
|
Self {
|
||||||
|
discord_user_id: value.discord_user_id as i64,
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user