Create client to establish connection with discord and add basic framework with sample command
This commit is contained in:
31
cipher_database/Cargo.toml
Normal file
31
cipher_database/Cargo.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "cipher_database"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.85"
|
||||
diesel = { version = "2.2.6", default-features = false }
|
||||
diesel-async = { version = "0.5.2", features = ["bb8"] }
|
||||
diesel_migrations = "2.2.0"
|
||||
cipher_core = { path = "../cipher_core" }
|
||||
thiserror = "2.0.11"
|
||||
|
||||
[features]
|
||||
default = ["mysql", "postgres", "sqlite"]
|
||||
mysql = [
|
||||
"diesel/mysql",
|
||||
"diesel-async/mysql",
|
||||
"diesel_migrations/mysql",
|
||||
]
|
||||
postgres = [
|
||||
"diesel/postgres",
|
||||
"diesel-async/postgres",
|
||||
"diesel_migrations/postgres",
|
||||
]
|
||||
sqlite = [
|
||||
"diesel/sqlite",
|
||||
"diesel/returning_clauses_for_sqlite_3_35",
|
||||
"diesel-async/sync-connection-wrapper",
|
||||
"diesel_migrations/sqlite",
|
||||
]
|
||||
3
cipher_database/build.rs
Normal file
3
cipher_database/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=migrations");
|
||||
}
|
||||
9
cipher_database/diesel_mysql.toml
Normal file
9
cipher_database/diesel_mysql.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
# For documentation on how to configure this file,
|
||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/mysql/schema.rs"
|
||||
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||
|
||||
[migrations_directory]
|
||||
dir = "migrations/mysql/"
|
||||
9
cipher_database/diesel_postgres.toml
Normal file
9
cipher_database/diesel_postgres.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
# For documentation on how to configure this file,
|
||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/postgres/schema.rs"
|
||||
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||
|
||||
[migrations_directory]
|
||||
dir = "migrations/postgres/"
|
||||
9
cipher_database/diesel_sqlite.toml
Normal file
9
cipher_database/diesel_sqlite.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
# For documentation on how to configure this file,
|
||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/sqlite/schema.rs"
|
||||
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||
|
||||
[migrations_directory]
|
||||
dir = "migrations/sqlite/"
|
||||
0
cipher_database/migrations/mysql/.keep
Normal file
0
cipher_database/migrations/mysql/.keep
Normal file
@@ -0,0 +1,3 @@
|
||||
DROP INDEX users_discord_user_id ON users;
|
||||
|
||||
DROP TABLE users;
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE users (
|
||||
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||
discord_user_id BIGINT NOT NULL,
|
||||
pokemon_go_code VARCHAR(32),
|
||||
pokemon_pocket_code VARCHAR(32),
|
||||
switch_code VARCHAR(32)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX users_discord_user_id ON users(discord_user_id);
|
||||
0
cipher_database/migrations/postgres/.keep
Normal file
0
cipher_database/migrations/postgres/.keep
Normal file
@@ -0,0 +1,6 @@
|
||||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
||||
@@ -0,0 +1,36 @@
|
||||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
|
||||
|
||||
|
||||
-- Sets up a trigger for the given table to automatically set a column called
|
||||
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||
-- in the modified columns)
|
||||
--
|
||||
-- # Example
|
||||
--
|
||||
-- ```sql
|
||||
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||
--
|
||||
-- SELECT diesel_manage_updated_at('users');
|
||||
-- ```
|
||||
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF (
|
||||
NEW IS DISTINCT FROM OLD AND
|
||||
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||
) THEN
|
||||
NEW.updated_at := current_timestamp;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -0,0 +1,3 @@
|
||||
DROP INDEX users_discord_user_id;
|
||||
|
||||
DROP TABLE users;
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
discord_user_id BIGINT NOT NULL,
|
||||
pokemon_go_code VARCHAR(32),
|
||||
pokemon_pocket_code VARCHAR(32),
|
||||
switch_code VARCHAR(32)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX users_discord_user_id ON users(discord_user_id);
|
||||
0
cipher_database/migrations/sqlite/.keep
Normal file
0
cipher_database/migrations/sqlite/.keep
Normal file
@@ -0,0 +1,3 @@
|
||||
DROP INDEX users_discord_user_id;
|
||||
|
||||
DROP TABLE users;
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE users (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
discord_user_id BIGINT NOT NULL,
|
||||
pokemon_go_code VARCHAR(32),
|
||||
pokemon_pocket_code VARCHAR(32),
|
||||
switch_code VARCHAR(32)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX users_discord_user_id ON users(discord_user_id);
|
||||
38
cipher_database/src/lib.rs
Normal file
38
cipher_database/src/lib.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
#[cfg(feature = "mysql")]
|
||||
pub mod mysql;
|
||||
#[cfg(feature = "postgres")]
|
||||
pub mod postgres;
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub mod sqlite;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DatabaseDialect {
|
||||
#[cfg(feature = "mysql")]
|
||||
Mysql,
|
||||
#[cfg(feature = "postgres")]
|
||||
Postgres,
|
||||
#[cfg(feature = "sqlite")]
|
||||
Sqlite,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BackendError {
|
||||
#[error(transparent)]
|
||||
DieselConnectionError(#[from] diesel::ConnectionError),
|
||||
#[error(transparent)]
|
||||
DieselQueryError(#[from] diesel::result::Error),
|
||||
#[error(transparent)]
|
||||
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 {
|
||||
fn from(value: diesel_async::pooled_connection::PoolError) -> Self {
|
||||
use diesel_async::pooled_connection::PoolError as E;
|
||||
match value {
|
||||
E::ConnectionError(connection_error) => Self::from(connection_error),
|
||||
E::QueryError(error) => Self::from(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
29
cipher_database/src/mysql/mod.rs
Normal file
29
cipher_database/src/mysql/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use diesel::Connection;
|
||||
use diesel::MysqlConnection;
|
||||
use diesel_async::pooled_connection::bb8::Pool;
|
||||
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
||||
use diesel_migrations::embed_migrations;
|
||||
use diesel_migrations::EmbeddedMigrations;
|
||||
use diesel_migrations::MigrationHarness;
|
||||
use repository::MysqlRepositoryProvider;
|
||||
|
||||
use crate::BackendError;
|
||||
|
||||
pub mod repository;
|
||||
mod schema;
|
||||
|
||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/mysql");
|
||||
|
||||
pub fn run_pending_migrations(database_url: &str) -> Result<(), BackendError> {
|
||||
let mut connection = MysqlConnection::establish(database_url)?;
|
||||
connection
|
||||
.run_pending_migrations(MIGRATIONS)
|
||||
.map_err(BackendError::DieselMigrationError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn repository_provider(database_url: &str) -> Result<MysqlRepositoryProvider, BackendError> {
|
||||
let config = AsyncDieselConnectionManager::new(database_url);
|
||||
let pool = Pool::builder().build(config).await?;
|
||||
Ok(MysqlRepositoryProvider::new(pool))
|
||||
}
|
||||
49
cipher_database/src/mysql/repository/mod.rs
Normal file
49
cipher_database/src/mysql/repository/mod.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use diesel_async::pooled_connection::bb8::Pool;
|
||||
use diesel_async::pooled_connection::bb8::PooledConnection;
|
||||
use diesel_async::AsyncMysqlConnection;
|
||||
use cipher_core::repository::Repository;
|
||||
use cipher_core::repository::RepositoryError;
|
||||
use cipher_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<'_>, RepositoryError<Self::BackendError>> {
|
||||
self.pool
|
||||
.get()
|
||||
.await
|
||||
.map(MysqlRepository::new)
|
||||
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
}
|
||||
161
cipher_database/src/mysql/repository/user_repository.rs
Normal file
161
cipher_database/src/mysql/repository/user_repository.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::scoped_futures::ScopedFutureExt;
|
||||
use diesel_async::AsyncConnection;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use cipher_core::repository::user_repository::NewUser;
|
||||
use cipher_core::repository::user_repository::User;
|
||||
use cipher_core::repository::user_repository::UserRepository;
|
||||
use cipher_core::repository::RepositoryError;
|
||||
|
||||
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>, RepositoryError<Self::BackendError>> {
|
||||
users::dsl::users.find(id)
|
||||
.first::<ModelUser>(&mut self.conn)
|
||||
.await
|
||||
.optional()
|
||||
.map(|option| option.map(User::from))
|
||||
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
|
||||
async fn insert_user(&mut self, new_user: NewUser) -> Result<User, RepositoryError<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(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
|
||||
async fn update_user(&mut self, user: User) -> Result<Option<User>, RepositoryError<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(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
|
||||
async fn remove_user(&mut self, id: i32) -> Result<Option<User>, RepositoryError<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(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
14
cipher_database/src/mysql/schema.rs
Normal file
14
cipher_database/src/mysql/schema.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
users (id) {
|
||||
id -> Integer,
|
||||
discord_user_id -> Bigint,
|
||||
#[max_length = 32]
|
||||
pokemon_go_code -> Nullable<Varchar>,
|
||||
#[max_length = 32]
|
||||
pokemon_pocket_code -> Nullable<Varchar>,
|
||||
#[max_length = 32]
|
||||
switch_code -> Nullable<Varchar>,
|
||||
}
|
||||
}
|
||||
29
cipher_database/src/postgres/mod.rs
Normal file
29
cipher_database/src/postgres/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use diesel::Connection;
|
||||
use diesel::PgConnection;
|
||||
use diesel_async::pooled_connection::bb8::Pool;
|
||||
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
||||
use diesel_migrations::embed_migrations;
|
||||
use diesel_migrations::EmbeddedMigrations;
|
||||
use diesel_migrations::MigrationHarness;
|
||||
use repository::PostgresRepositoryProvider;
|
||||
|
||||
use crate::BackendError;
|
||||
|
||||
pub mod repository;
|
||||
mod schema;
|
||||
|
||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/postgres");
|
||||
|
||||
pub fn run_pending_migrations(database_url: &str) -> Result<(), BackendError> {
|
||||
let mut connection = PgConnection::establish(database_url)?;
|
||||
connection
|
||||
.run_pending_migrations(MIGRATIONS)
|
||||
.map_err(BackendError::DieselMigrationError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn repository_provider(database_url: &str) -> Result<PostgresRepositoryProvider, BackendError> {
|
||||
let config = AsyncDieselConnectionManager::new(database_url);
|
||||
let pool = Pool::builder().build(config).await?;
|
||||
Ok(PostgresRepositoryProvider::new(pool))
|
||||
}
|
||||
49
cipher_database/src/postgres/repository/mod.rs
Normal file
49
cipher_database/src/postgres/repository/mod.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use diesel_async::pooled_connection::bb8::Pool;
|
||||
use diesel_async::pooled_connection::bb8::PooledConnection;
|
||||
use diesel_async::AsyncPgConnection;
|
||||
use cipher_core::repository::Repository;
|
||||
use cipher_core::repository::RepositoryError;
|
||||
use cipher_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<'_>, RepositoryError<Self::BackendError>> {
|
||||
self.pool
|
||||
.get()
|
||||
.await
|
||||
.map(PostgresRepository::new)
|
||||
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
}
|
||||
144
cipher_database/src/postgres/repository/user_repository.rs
Normal file
144
cipher_database/src/postgres/repository/user_repository.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::scoped_futures::ScopedFutureExt;
|
||||
use diesel_async::AsyncConnection;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use cipher_core::repository::user_repository::NewUser;
|
||||
use cipher_core::repository::user_repository::User;
|
||||
use cipher_core::repository::user_repository::UserRepository;
|
||||
use cipher_core::repository::RepositoryError;
|
||||
|
||||
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>, RepositoryError<Self::BackendError>> {
|
||||
users::dsl::users.find(id)
|
||||
.first::<ModelUser>(&mut self.conn)
|
||||
.await
|
||||
.optional()
|
||||
.map(|option| option.map(User::from))
|
||||
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
|
||||
async fn insert_user(&mut self, new_user: NewUser) -> Result<User, RepositoryError<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(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
|
||||
async fn update_user(&mut self, user: User) -> Result<Option<User>, RepositoryError<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(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
|
||||
async fn remove_user(&mut self, id: i32) -> Result<Option<User>, RepositoryError<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(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
14
cipher_database/src/postgres/schema.rs
Normal file
14
cipher_database/src/postgres/schema.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
users (id) {
|
||||
id -> Int4,
|
||||
discord_user_id -> Int8,
|
||||
#[max_length = 32]
|
||||
pokemon_go_code -> Nullable<Varchar>,
|
||||
#[max_length = 32]
|
||||
pokemon_pocket_code -> Nullable<Varchar>,
|
||||
#[max_length = 32]
|
||||
switch_code -> Nullable<Varchar>,
|
||||
}
|
||||
}
|
||||
29
cipher_database/src/sqlite/mod.rs
Normal file
29
cipher_database/src/sqlite/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use diesel::Connection;
|
||||
use diesel::SqliteConnection;
|
||||
use diesel_async::pooled_connection::bb8::Pool;
|
||||
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
|
||||
use diesel_migrations::embed_migrations;
|
||||
use diesel_migrations::EmbeddedMigrations;
|
||||
use diesel_migrations::MigrationHarness;
|
||||
use repository::SqliteRepositoryProvider;
|
||||
|
||||
use crate::BackendError;
|
||||
|
||||
pub mod repository;
|
||||
mod schema;
|
||||
|
||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/sqlite");
|
||||
|
||||
pub fn run_pending_migrations(database_url: &str) -> Result<(), BackendError> {
|
||||
let mut connection = SqliteConnection::establish(database_url)?;
|
||||
connection
|
||||
.run_pending_migrations(MIGRATIONS)
|
||||
.map_err(BackendError::DieselMigrationError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn repository_provider(database_url: &str) -> Result<SqliteRepositoryProvider, BackendError> {
|
||||
let config = AsyncDieselConnectionManager::new(database_url);
|
||||
let pool = Pool::builder().build(config).await?;
|
||||
Ok(SqliteRepositoryProvider::new(pool))
|
||||
}
|
||||
50
cipher_database/src/sqlite/repository/mod.rs
Normal file
50
cipher_database/src/sqlite/repository/mod.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
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 cipher_core::repository::Repository;
|
||||
use cipher_core::repository::RepositoryError;
|
||||
use cipher_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<'_>, RepositoryError<Self::BackendError>> {
|
||||
self.pool
|
||||
.get()
|
||||
.await
|
||||
.map(SqliteRepository::new)
|
||||
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
}
|
||||
144
cipher_database/src/sqlite/repository/user_repository.rs
Normal file
144
cipher_database/src/sqlite/repository/user_repository.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::scoped_futures::ScopedFutureExt;
|
||||
use diesel_async::AsyncConnection;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use cipher_core::repository::user_repository::NewUser;
|
||||
use cipher_core::repository::user_repository::User;
|
||||
use cipher_core::repository::user_repository::UserRepository;
|
||||
use cipher_core::repository::RepositoryError;
|
||||
|
||||
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>, RepositoryError<Self::BackendError>> {
|
||||
users::dsl::users.find(id)
|
||||
.first::<ModelUser>(&mut self.conn)
|
||||
.await
|
||||
.optional()
|
||||
.map(|option| option.map(User::from))
|
||||
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
|
||||
async fn insert_user(&mut self, new_user: NewUser) -> Result<User, RepositoryError<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(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
|
||||
async fn update_user(&mut self, user: User) -> Result<Option<User>, RepositoryError<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(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
|
||||
async fn remove_user(&mut self, id: i32) -> Result<Option<User>, RepositoryError<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(|err| RepositoryError(BackendError::from(err)))
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
11
cipher_database/src/sqlite/schema.rs
Normal file
11
cipher_database/src/sqlite/schema.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
users (id) {
|
||||
id -> Integer,
|
||||
discord_user_id -> BigInt,
|
||||
pokemon_go_code -> Nullable<Text>,
|
||||
pokemon_pocket_code -> Nullable<Text>,
|
||||
switch_code -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user