Add profiles table
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -369,8 +369,10 @@ checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -379,6 +381,7 @@ name = "cipher_core"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -386,6 +389,7 @@ name = "cipher_database"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
"cipher_core",
|
"cipher_core",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel-async",
|
"diesel-async",
|
||||||
@@ -710,6 +714,7 @@ checksum = "ccf1bedf64cdb9643204a36dd15b19a6ce8e7aa7f7b105868e9f1fad5ffa7d12"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"chrono",
|
||||||
"diesel_derives",
|
"diesel_derives",
|
||||||
"itoa",
|
"itoa",
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = "0.1.85"
|
async-trait = "0.1.85"
|
||||||
|
chrono = "0.4.39"
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use profile_repository::ProfileRepository;
|
||||||
use staff_role_repository::StaffRoleRepository;
|
use staff_role_repository::StaffRoleRepository;
|
||||||
use user_repository::UserRepository;
|
use user_repository::UserRepository;
|
||||||
|
|
||||||
|
pub mod profile_repository;
|
||||||
pub mod staff_role_repository;
|
pub mod staff_role_repository;
|
||||||
pub mod user_repository;
|
pub mod user_repository;
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ pub trait RepositoryProvider {
|
|||||||
|
|
||||||
pub trait Repository
|
pub trait Repository
|
||||||
where
|
where
|
||||||
|
Self: ProfileRepository<BackendError = <Self as Repository>::BackendError>,
|
||||||
Self: StaffRoleRepository<BackendError = <Self as Repository>::BackendError>,
|
Self: StaffRoleRepository<BackendError = <Self as Repository>::BackendError>,
|
||||||
Self: UserRepository<BackendError = <Self as Repository>::BackendError>,
|
Self: UserRepository<BackendError = <Self as Repository>::BackendError>,
|
||||||
{
|
{
|
||||||
|
|||||||
165
cipher_core/src/repository/profile_repository.rs
Normal file
165
cipher_core/src/repository/profile_repository.rs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
use chrono::DateTime;
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
|
use super::RepositoryError;
|
||||||
|
|
||||||
|
/// A repository trait for managing user profiles, supporting asynchronous operations.
|
||||||
|
///
|
||||||
|
/// This trait defines the required operations for interacting with user profiles,
|
||||||
|
/// including creation, retrieval, history management, and version rollback. It is
|
||||||
|
/// designed to be implemented for various backends, such as databases, using Diesel or Diesel Async.
|
||||||
|
///
|
||||||
|
/// # Type Parameters
|
||||||
|
/// - `BackendError`: Represents the error type returned by the underlying backend implementation.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// All methods return a `RepositoryError<Self::BackendError>`, which encapsulates
|
||||||
|
/// errors specific to the backend implementation.
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait ProfileRepository {
|
||||||
|
/// The associated error type returned by backend operations.
|
||||||
|
type BackendError: std::error::Error;
|
||||||
|
|
||||||
|
/// Inserts a new profile into the repository and marks it as the active profile for the user it belongs to.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `new_profile` - The profile data to insert.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(Profile)` - The inserted profile with its assigned ID.
|
||||||
|
/// * `Err(RepositoryError<Self::BackendError>)` - If the operation fails.
|
||||||
|
async fn insert_profile(&mut self, new_profile: NewProfile) -> Result<Profile, RepositoryError<Self::BackendError>>;
|
||||||
|
|
||||||
|
/// Retrieves a profile by its unique ID.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `id` - The unique profile ID.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(Some(Profile))` - If a profile with the given ID exists.
|
||||||
|
/// * `Ok(None)` - If no profile is found.
|
||||||
|
/// * `Err(RepositoryError<Self::BackendError>)` - If an error occurs.
|
||||||
|
async fn profile(&mut self, id: i32) -> Result<Option<Profile>, RepositoryError<Self::BackendError>>;
|
||||||
|
|
||||||
|
/// Retrieves the active profile associated with a given user ID.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `user_id` - The ID of the user whose profile is being retrieved.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(Some(Profile))` - The users active profile for the user if it exists.
|
||||||
|
/// * `Ok(None)` - If no active profile is found for the user.
|
||||||
|
/// * `Err(RepositoryError<Self::BackendError>)` - If an error occurs.
|
||||||
|
async fn active_profile(&mut self, user_id: i32) -> Result<Option<Profile>, RepositoryError<Self::BackendError>>;
|
||||||
|
|
||||||
|
/// Retrieves the active profile associated with a given Discord user ID.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `discord_user_id` - The Discord user's unique identifier.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(Some(Profile))` - The users active profile for the user if it exists.
|
||||||
|
/// * `Ok(None)` - If no active profile is found for the user.
|
||||||
|
/// * `Err(RepositoryError<Self::BackendError>)` - If an error occurs.
|
||||||
|
async fn active_profile_by_discord_id(&mut self, discord_user_id: u64) -> Result<Option<Profile>, RepositoryError<Self::BackendError>>;
|
||||||
|
|
||||||
|
/// Retrieves the full profile history for a given user.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `user_id` - The ID of the user whose profile history is being retrieved.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(Vec<Profile>)` - A list of all past versions of the user's profile.
|
||||||
|
/// * `Err(RepositoryError<Self::BackendError>)` - If an error occurs.
|
||||||
|
async fn profiles_by_user_id(&mut self, user_id: i32) -> Result<Vec<Profile>, RepositoryError<Self::BackendError>>;
|
||||||
|
|
||||||
|
/// Retrieves the full profile history for a given Discord user.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `discord_user_id` - The Discord user's unique identifier.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(Vec<Profile>)` - A list of all past versions of the user's profile.
|
||||||
|
/// * `Err(RepositoryError<Self::BackendError>)` - If an error occurs.
|
||||||
|
async fn profiles_by_discord_id(&mut self, discord_user_id: u64) -> Result<Vec<Profile>, RepositoryError<Self::BackendError>>;
|
||||||
|
|
||||||
|
/// Sets an profile version as active.
|
||||||
|
///
|
||||||
|
/// This function marks `profile_id` as the active profile
|
||||||
|
/// and deactivates all other profiles for the user.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `user_id` - The ID of the user.
|
||||||
|
/// * `profile_id` - The profile ID to mark as active.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Ok(false)` - If the specified profile does not exist.
|
||||||
|
/// * `Ok(true)` - If the operation was successful.
|
||||||
|
/// * `Err(RepositoryError<Self::BackendError>)` - If the operation fails.
|
||||||
|
async fn set_active_profile(&mut self, user_id: i32, profile_id: i32) -> Result<bool, RepositoryError<Self::BackendError>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Profile {
|
||||||
|
pub id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
|
||||||
|
pub thumbnail_url: Option<String>,
|
||||||
|
pub image_url: Option<String>,
|
||||||
|
|
||||||
|
pub trainer_class: Option<String>,
|
||||||
|
pub nature: Option<String>,
|
||||||
|
pub partner_pokemon: Option<String>,
|
||||||
|
pub starting_region: Option<String>,
|
||||||
|
pub favourite_food: Option<String>,
|
||||||
|
pub likes: Option<String>,
|
||||||
|
pub quotes: Option<String>,
|
||||||
|
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NewProfile {
|
||||||
|
pub user_id: i32,
|
||||||
|
|
||||||
|
pub thumbnail_url: Option<String>,
|
||||||
|
pub image_url: Option<String>,
|
||||||
|
|
||||||
|
pub trainer_class: Option<String>,
|
||||||
|
pub nature: Option<String>,
|
||||||
|
pub partner_pokemon: Option<String>,
|
||||||
|
pub starting_region: Option<String>,
|
||||||
|
pub favourite_food: Option<String>,
|
||||||
|
pub likes: Option<String>,
|
||||||
|
pub quotes: Option<String>,
|
||||||
|
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Profile {
|
||||||
|
pub fn into_new(self) -> NewProfile {
|
||||||
|
NewProfile {
|
||||||
|
user_id: self.user_id,
|
||||||
|
|
||||||
|
thumbnail_url: self.thumbnail_url,
|
||||||
|
image_url: self.image_url,
|
||||||
|
|
||||||
|
trainer_class: self.trainer_class,
|
||||||
|
nature: self.nature,
|
||||||
|
partner_pokemon: self.partner_pokemon,
|
||||||
|
starting_region: self.starting_region,
|
||||||
|
favourite_food: self.favourite_food,
|
||||||
|
likes: self.likes,
|
||||||
|
quotes: self.quotes,
|
||||||
|
|
||||||
|
pokemon_go_code: self.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: self.pokemon_pocket_code,
|
||||||
|
switch_code: self.switch_code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,21 +11,13 @@ pub trait UserRepository {
|
|||||||
async fn insert_user(&mut self, new_user: NewUser) -> Result<User, RepositoryError<Self::BackendError>>;
|
async fn insert_user(&mut self, new_user: NewUser) -> Result<User, RepositoryError<Self::BackendError>>;
|
||||||
|
|
||||||
async fn update_user(&mut self, user: User) -> Result<Option<User>, RepositoryError<Self::BackendError>>;
|
async fn update_user(&mut self, user: User) -> Result<Option<User>, RepositoryError<Self::BackendError>>;
|
||||||
|
|
||||||
async fn remove_user(&mut self, id: i32) -> Result<Option<User>, RepositoryError<Self::BackendError>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub discord_user_id: u64,
|
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 struct NewUser {
|
||||||
pub discord_user_id: u64,
|
pub discord_user_id: u64,
|
||||||
pub pokemon_go_code: Option<String>,
|
|
||||||
pub pokemon_pocket_code: Option<String>,
|
|
||||||
pub switch_code: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = "0.1.85"
|
async-trait = "0.1.85"
|
||||||
diesel = { version = "2.2.6", default-features = false }
|
diesel = { version = "2.2.6", default-features = false, features = ["chrono"] }
|
||||||
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"
|
||||||
cipher_core = { path = "../cipher_core" }
|
cipher_core = { path = "../cipher_core" }
|
||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
|
chrono = "0.4.39"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mysql", "postgres", "sqlite"]
|
default = ["mysql", "postgres", "sqlite"]
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN pokemon_go_code VARCHAR(32);
|
||||||
|
ALTER TABLE users ADD COLUMN pokemon_pocket_code VARCHAR(32);
|
||||||
|
ALTER TABLE users ADD COLUMN switch_code VARCHAR(32);
|
||||||
|
|
||||||
|
UPDATE users
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT user_id, pokemon_go_code, pokemon_pocket_code, switch_code
|
||||||
|
FROM profiles
|
||||||
|
WHERE is_active = true
|
||||||
|
) AS subquery
|
||||||
|
ON users.id = subquery.user_id
|
||||||
|
SET
|
||||||
|
users.pokemon_go_code = subquery.pokemon_go_code,
|
||||||
|
users.pokemon_pocket_code = subquery.pokemon_pocket_code,
|
||||||
|
users.switch_code = subquery.switch_code;
|
||||||
|
|
||||||
|
DROP INDEX profiles_created_at ON profiles;
|
||||||
|
DROP INDEX profiles_is_active ON profiles;
|
||||||
|
|
||||||
|
DROP TABLE profiles;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
CREATE TABLE profiles (
|
||||||
|
id INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
thumbnail_url TEXT,
|
||||||
|
image_url TEXT,
|
||||||
|
|
||||||
|
trainer_class TEXT,
|
||||||
|
nature TEXT,
|
||||||
|
partner_pokemon TEXT,
|
||||||
|
starting_region TEXT,
|
||||||
|
favourite_food TEXT,
|
||||||
|
likes TEXT,
|
||||||
|
quotes TEXT,
|
||||||
|
|
||||||
|
pokemon_go_code VARCHAR(32),
|
||||||
|
pokemon_pocket_code VARCHAR(32),
|
||||||
|
switch_code VARCHAR(32),
|
||||||
|
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX profiles_user_id ON profiles(user_id);
|
||||||
|
CREATE INDEX profiles_created_at ON profiles(created_at);
|
||||||
|
CREATE INDEX profiles_is_active ON profiles(is_active);
|
||||||
|
|
||||||
|
INSERT INTO profiles (user_id, pokemon_go_code, pokemon_pocket_code, switch_code)
|
||||||
|
SELECT id, pokemon_go_code, pokemon_pocket_code, switch_code
|
||||||
|
FROM users;
|
||||||
|
|
||||||
|
ALTER TABLE users DROP COLUMN pokemon_go_code;
|
||||||
|
ALTER TABLE users DROP COLUMN pokemon_pocket_code;
|
||||||
|
ALTER TABLE users DROP COLUMN switch_code;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN pokemon_go_code VARCHAR(32);
|
||||||
|
ALTER TABLE users ADD COLUMN pokemon_pocket_code VARCHAR(32);
|
||||||
|
ALTER TABLE users ADD COLUMN switch_code VARCHAR(32);
|
||||||
|
|
||||||
|
UPDATE users
|
||||||
|
SET
|
||||||
|
pokemon_go_code = subquery.pokemon_go_code,
|
||||||
|
pokemon_pocket_code = subquery.pokemon_pocket_code,
|
||||||
|
switch_code = subquery.switch_code
|
||||||
|
FROM (
|
||||||
|
SELECT user_id, pokemon_go_code, pokemon_pocket_code, switch_code
|
||||||
|
FROM profiles
|
||||||
|
WHERE is_active = true
|
||||||
|
) AS subquery
|
||||||
|
WHERE id = subquery.user_id;
|
||||||
|
|
||||||
|
DROP INDEX profiles_user_id;
|
||||||
|
DROP INDEX profiles_created_at;
|
||||||
|
DROP INDEX profiles_is_active;
|
||||||
|
|
||||||
|
DROP TABLE profiles;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
CREATE TABLE profiles (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
|
||||||
|
thumbnail_url TEXT,
|
||||||
|
image_url TEXT,
|
||||||
|
|
||||||
|
trainer_class TEXT,
|
||||||
|
nature TEXT,
|
||||||
|
partner_pokemon TEXT,
|
||||||
|
starting_region TEXT,
|
||||||
|
favourite_food TEXT,
|
||||||
|
likes TEXT,
|
||||||
|
quotes TEXT,
|
||||||
|
|
||||||
|
pokemon_go_code VARCHAR(32),
|
||||||
|
pokemon_pocket_code VARCHAR(32),
|
||||||
|
switch_code VARCHAR(32),
|
||||||
|
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX profiles_user_id ON profiles(user_id);
|
||||||
|
CREATE INDEX profiles_created_at ON profiles(created_at);
|
||||||
|
CREATE INDEX profiles_is_active ON profiles(is_active);
|
||||||
|
|
||||||
|
INSERT INTO profiles (user_id, pokemon_go_code, pokemon_pocket_code, switch_code)
|
||||||
|
SELECT id, pokemon_go_code, pokemon_pocket_code, switch_code
|
||||||
|
FROM users;
|
||||||
|
|
||||||
|
ALTER TABLE users DROP COLUMN pokemon_go_code;
|
||||||
|
ALTER TABLE users DROP COLUMN pokemon_pocket_code;
|
||||||
|
ALTER TABLE users DROP COLUMN switch_code;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN pokemon_go_code VARCHAR(32);
|
||||||
|
ALTER TABLE users ADD COLUMN pokemon_pocket_code VARCHAR(32);
|
||||||
|
ALTER TABLE users ADD COLUMN switch_code VARCHAR(32);
|
||||||
|
|
||||||
|
UPDATE users SET
|
||||||
|
pokemon_go_code = subquery.pokemon_go_code,
|
||||||
|
pokemon_pocket_code = subquery.pokemon_pocket_code,
|
||||||
|
switch_code = subquery.switch_code
|
||||||
|
FROM (
|
||||||
|
SELECT user_id, pokemon_go_code, pokemon_pocket_code, switch_code
|
||||||
|
FROM profiles
|
||||||
|
WHERE is_active = true
|
||||||
|
) AS subquery
|
||||||
|
WHERE id = subquery.user_id;
|
||||||
|
|
||||||
|
DROP INDEX profiles_user_id;
|
||||||
|
DROP INDEX profiles_created_at;
|
||||||
|
DROP INDEX profiles_is_active;
|
||||||
|
|
||||||
|
DROP TABLE profiles;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
CREATE TABLE profiles (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
|
||||||
|
thumbnail_url TEXT,
|
||||||
|
image_url TEXT,
|
||||||
|
|
||||||
|
trainer_class TEXT,
|
||||||
|
nature TEXT,
|
||||||
|
partner_pokemon TEXT,
|
||||||
|
starting_region TEXT,
|
||||||
|
favourite_food TEXT,
|
||||||
|
likes TEXT,
|
||||||
|
quotes TEXT,
|
||||||
|
|
||||||
|
pokemon_go_code VARCHAR(32),
|
||||||
|
pokemon_pocket_code VARCHAR(32),
|
||||||
|
switch_code VARCHAR(32),
|
||||||
|
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX profiles_user_id ON profiles(user_id);
|
||||||
|
CREATE INDEX profiles_created_at ON profiles(created_at);
|
||||||
|
CREATE INDEX profiles_is_active ON profiles(is_active);
|
||||||
|
|
||||||
|
INSERT INTO profiles (user_id, pokemon_go_code, pokemon_pocket_code, switch_code)
|
||||||
|
SELECT id, pokemon_go_code, pokemon_pocket_code, switch_code
|
||||||
|
FROM users;
|
||||||
|
|
||||||
|
ALTER TABLE users DROP COLUMN pokemon_go_code;
|
||||||
|
ALTER TABLE users DROP COLUMN pokemon_pocket_code;
|
||||||
|
ALTER TABLE users DROP COLUMN switch_code;
|
||||||
@@ -7,6 +7,7 @@ use cipher_core::repository::RepositoryProvider;
|
|||||||
|
|
||||||
use crate::BackendError;
|
use crate::BackendError;
|
||||||
|
|
||||||
|
mod profile_repository;
|
||||||
mod staff_role_repository;
|
mod staff_role_repository;
|
||||||
mod user_repository;
|
mod user_repository;
|
||||||
|
|
||||||
|
|||||||
246
cipher_database/src/mysql/repository/profile_repository.rs
Normal file
246
cipher_database/src/mysql/repository/profile_repository.rs
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
use chrono::DateTime;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use chrono::Utc;
|
||||||
|
use cipher_core::repository::profile_repository::NewProfile;
|
||||||
|
use diesel_async::scoped_futures::ScopedFutureExt;
|
||||||
|
use diesel_async::AsyncConnection;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use cipher_core::repository::profile_repository::Profile;
|
||||||
|
use cipher_core::repository::profile_repository::ProfileRepository;
|
||||||
|
use cipher_core::repository::RepositoryError;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
use crate::mysql::schema::profiles;
|
||||||
|
use crate::mysql::schema::users;
|
||||||
|
use crate::BackendError;
|
||||||
|
|
||||||
|
use super::MysqlRepository;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ProfileRepository for MysqlRepository<'_> {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
|
||||||
|
async fn insert_profile(&mut self, new_profile: NewProfile) -> Result<Profile, RepositoryError<Self::BackendError>> {
|
||||||
|
let model_new_profile = ModelNewProfile::from(new_profile);
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(|conn| async move {
|
||||||
|
diesel::update(profiles::table)
|
||||||
|
.filter(profiles::user_id.eq(model_new_profile.user_id))
|
||||||
|
.set(profiles::is_active.eq(false))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
diesel::insert_into(profiles::table)
|
||||||
|
.values(&model_new_profile)
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
profiles::table
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.select(ModelProfile::as_select())
|
||||||
|
.first(conn)
|
||||||
|
.await
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn profile(&mut self, id: i32) -> Result<Option<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
profiles::dsl::profiles.find(id)
|
||||||
|
.first::<ModelProfile>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.optional()
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn active_profile(&mut self, user_id: i32) -> Result<Option<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
profiles::dsl::profiles
|
||||||
|
.filter(profiles::user_id.eq(user_id))
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.first::<ModelProfile>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.optional()
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn active_profile_by_discord_id(&mut self, discord_user_id: u64) -> Result<Option<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
let model_discord_user_id = discord_user_id as i64;
|
||||||
|
profiles::table
|
||||||
|
.inner_join(users::table)
|
||||||
|
.filter(users::discord_user_id.eq(model_discord_user_id))
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.select(ModelProfile::as_select())
|
||||||
|
.first(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.optional()
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn profiles_by_user_id(&mut self, user_id: i32) -> Result<Vec<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
let results: Vec<_> = profiles::dsl::profiles
|
||||||
|
.filter(profiles::user_id.eq(user_id))
|
||||||
|
.order(profiles::created_at.desc())
|
||||||
|
.get_results::<ModelProfile>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))?
|
||||||
|
.into_iter()
|
||||||
|
.map(Profile::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn profiles_by_discord_id(&mut self, discord_user_id: u64) -> Result<Vec<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
let model_discord_user_id = discord_user_id as i64;
|
||||||
|
let results = profiles::table
|
||||||
|
.inner_join(users::table)
|
||||||
|
.filter(users::discord_user_id.eq(model_discord_user_id))
|
||||||
|
.order(profiles::created_at.desc())
|
||||||
|
.select(ModelProfile::as_select())
|
||||||
|
.get_results(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))?
|
||||||
|
.into_iter()
|
||||||
|
.map(Profile::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_active_profile(&mut self, user_id: i32, profile_id: i32) -> Result<bool, RepositoryError<Self::BackendError>> {
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(move |conn| async move {
|
||||||
|
let num_affected = diesel::update(profiles::table.find(profile_id))
|
||||||
|
.set(profiles::is_active.eq(true))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if num_affected == 0 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::update(profiles::table)
|
||||||
|
.filter(profiles::user_id.eq(user_id))
|
||||||
|
.filter(profiles::id.ne(profile_id))
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.set(profiles::is_active.eq(false))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, AsChangeset)]
|
||||||
|
#[diesel(table_name = profiles)]
|
||||||
|
#[diesel(belongs_to(ModelUser))]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||||
|
pub struct ModelProfile {
|
||||||
|
pub id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
|
||||||
|
pub thumbnail_url: Option<String>,
|
||||||
|
pub image_url: Option<String>,
|
||||||
|
|
||||||
|
pub trainer_class: Option<String>,
|
||||||
|
pub nature: Option<String>,
|
||||||
|
pub partner_pokemon: Option<String>,
|
||||||
|
pub starting_region: Option<String>,
|
||||||
|
pub favourite_food: Option<String>,
|
||||||
|
pub likes: Option<String>,
|
||||||
|
pub quotes: Option<String>,
|
||||||
|
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModelProfile> for Profile {
|
||||||
|
fn from(value: ModelProfile) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
user_id: value.user_id,
|
||||||
|
|
||||||
|
thumbnail_url: value.thumbnail_url,
|
||||||
|
image_url: value.image_url,
|
||||||
|
|
||||||
|
trainer_class: value.trainer_class,
|
||||||
|
nature: value.nature,
|
||||||
|
partner_pokemon: value.partner_pokemon,
|
||||||
|
starting_region: value.starting_region,
|
||||||
|
favourite_food: value.favourite_food,
|
||||||
|
likes: value.likes,
|
||||||
|
quotes: value.quotes,
|
||||||
|
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
|
||||||
|
created_at: DateTime::from_naive_utc_and_offset(value.created_at, Utc),
|
||||||
|
is_active: value.is_active,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = profiles)]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||||
|
pub struct ModelNewProfile {
|
||||||
|
pub user_id: i32,
|
||||||
|
|
||||||
|
pub thumbnail_url: Option<String>,
|
||||||
|
pub image_url: Option<String>,
|
||||||
|
|
||||||
|
pub trainer_class: Option<String>,
|
||||||
|
pub nature: Option<String>,
|
||||||
|
pub partner_pokemon: Option<String>,
|
||||||
|
pub starting_region: Option<String>,
|
||||||
|
pub favourite_food: Option<String>,
|
||||||
|
pub likes: Option<String>,
|
||||||
|
pub quotes: Option<String>,
|
||||||
|
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NewProfile> for ModelNewProfile {
|
||||||
|
fn from(value: NewProfile) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: value.user_id,
|
||||||
|
|
||||||
|
thumbnail_url: value.thumbnail_url,
|
||||||
|
image_url: value.image_url,
|
||||||
|
|
||||||
|
trainer_class: value.trainer_class,
|
||||||
|
nature: value.nature,
|
||||||
|
partner_pokemon: value.partner_pokemon,
|
||||||
|
starting_region: value.starting_region,
|
||||||
|
favourite_food: value.favourite_food,
|
||||||
|
likes: value.likes,
|
||||||
|
quotes: value.quotes,
|
||||||
|
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
is_active: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,7 +91,7 @@ impl StaffRoleRepository for MysqlRepository<'_> {
|
|||||||
#[diesel(table_name = staff_roles)]
|
#[diesel(table_name = staff_roles)]
|
||||||
#[diesel(treat_none_as_null = true)]
|
#[diesel(treat_none_as_null = true)]
|
||||||
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||||
struct ModelStaffRole {
|
pub struct ModelStaffRole {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
id: i32,
|
id: i32,
|
||||||
discord_role_id: i64,
|
discord_role_id: i64,
|
||||||
@@ -101,6 +101,6 @@ struct ModelStaffRole {
|
|||||||
#[diesel(table_name = staff_roles)]
|
#[diesel(table_name = staff_roles)]
|
||||||
#[diesel(treat_none_as_null = true)]
|
#[diesel(treat_none_as_null = true)]
|
||||||
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||||
struct ModelNewStaffRole {
|
pub struct ModelNewStaffRole {
|
||||||
discord_role_id: i64,
|
discord_role_id: i64,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,44 +86,15 @@ impl UserRepository for MysqlRepository<'_> {
|
|||||||
.map(|option| option.map(User::from))
|
.map(|option| option.map(User::from))
|
||||||
.map_err(|err| RepositoryError(BackendError::from(err)))
|
.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)]
|
#[derive(Queryable, Selectable, AsChangeset)]
|
||||||
#[diesel(table_name = users)]
|
#[diesel(table_name = users)]
|
||||||
#[diesel(treat_none_as_null = true)]
|
#[diesel(treat_none_as_null = true)]
|
||||||
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||||
struct ModelUser {
|
pub struct ModelUser {
|
||||||
id: i32,
|
id: i32,
|
||||||
discord_user_id: i64,
|
discord_user_id: i64,
|
||||||
pokemon_go_code: Option<String>,
|
|
||||||
pokemon_pocket_code: Option<String>,
|
|
||||||
switch_code: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ModelUser> for User {
|
impl From<ModelUser> for User {
|
||||||
@@ -131,9 +102,6 @@ impl From<ModelUser> for User {
|
|||||||
Self {
|
Self {
|
||||||
id: value.id,
|
id: value.id,
|
||||||
discord_user_id: value.discord_user_id as u64,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,9 +111,6 @@ impl From<User> for ModelUser {
|
|||||||
Self {
|
Self {
|
||||||
id: value.id,
|
id: value.id,
|
||||||
discord_user_id: value.discord_user_id as i64,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,20 +119,14 @@ impl From<User> for ModelUser {
|
|||||||
#[diesel(table_name = users)]
|
#[diesel(table_name = users)]
|
||||||
#[diesel(treat_none_as_null = true)]
|
#[diesel(treat_none_as_null = true)]
|
||||||
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
#[diesel(check_for_backend(diesel::mysql::Mysql))]
|
||||||
struct ModelNewUser {
|
pub struct ModelNewUser {
|
||||||
discord_user_id: i64,
|
discord_user_id: i64,
|
||||||
pokemon_go_code: Option<String>,
|
|
||||||
pokemon_pocket_code: Option<String>,
|
|
||||||
switch_code: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NewUser> for ModelNewUser {
|
impl From<NewUser> for ModelNewUser {
|
||||||
fn from(value: NewUser) -> Self {
|
fn from(value: NewUser) -> Self {
|
||||||
Self {
|
Self {
|
||||||
discord_user_id: value.discord_user_id as i64,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
// @generated automatically by Diesel CLI.
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
profiles (id) {
|
||||||
|
id -> Integer,
|
||||||
|
user_id -> Integer,
|
||||||
|
thumbnail_url -> Nullable<Text>,
|
||||||
|
image_url -> Nullable<Text>,
|
||||||
|
trainer_class -> Nullable<Text>,
|
||||||
|
nature -> Nullable<Text>,
|
||||||
|
partner_pokemon -> Nullable<Text>,
|
||||||
|
starting_region -> Nullable<Text>,
|
||||||
|
favourite_food -> Nullable<Text>,
|
||||||
|
likes -> Nullable<Text>,
|
||||||
|
quotes -> Nullable<Text>,
|
||||||
|
#[max_length = 32]
|
||||||
|
pokemon_go_code -> Nullable<Varchar>,
|
||||||
|
#[max_length = 32]
|
||||||
|
pokemon_pocket_code -> Nullable<Varchar>,
|
||||||
|
#[max_length = 32]
|
||||||
|
switch_code -> Nullable<Varchar>,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
is_active -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
staff_roles (id) {
|
staff_roles (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
@@ -11,16 +35,13 @@ diesel::table! {
|
|||||||
users (id) {
|
users (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
discord_user_id -> Bigint,
|
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>,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::joinable!(profiles -> users (user_id));
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
profiles,
|
||||||
staff_roles,
|
staff_roles,
|
||||||
users,
|
users,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use cipher_core::repository::RepositoryProvider;
|
|||||||
|
|
||||||
use crate::BackendError;
|
use crate::BackendError;
|
||||||
|
|
||||||
|
mod profile_repository;
|
||||||
mod staff_role_repository;
|
mod staff_role_repository;
|
||||||
mod user_repository;
|
mod user_repository;
|
||||||
|
|
||||||
|
|||||||
241
cipher_database/src/postgres/repository/profile_repository.rs
Normal file
241
cipher_database/src/postgres/repository/profile_repository.rs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
use chrono::DateTime;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use chrono::Utc;
|
||||||
|
use cipher_core::repository::profile_repository::NewProfile;
|
||||||
|
use diesel_async::scoped_futures::ScopedFutureExt;
|
||||||
|
use diesel_async::AsyncConnection;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use cipher_core::repository::profile_repository::Profile;
|
||||||
|
use cipher_core::repository::profile_repository::ProfileRepository;
|
||||||
|
use cipher_core::repository::RepositoryError;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
use crate::postgres::schema::profiles;
|
||||||
|
use crate::postgres::schema::users;
|
||||||
|
use crate::BackendError;
|
||||||
|
|
||||||
|
use super::PostgresRepository;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ProfileRepository for PostgresRepository<'_> {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
|
||||||
|
async fn insert_profile(&mut self, new_profile: NewProfile) -> Result<Profile, RepositoryError<Self::BackendError>> {
|
||||||
|
let model_new_profile = ModelNewProfile::from(new_profile);
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(|conn| async move {
|
||||||
|
diesel::update(profiles::table)
|
||||||
|
.filter(profiles::user_id.eq(model_new_profile.user_id))
|
||||||
|
.set(profiles::is_active.eq(false))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
diesel::insert_into(profiles::table)
|
||||||
|
.values(&model_new_profile)
|
||||||
|
.returning(ModelProfile::as_returning())
|
||||||
|
.get_result(conn)
|
||||||
|
.await
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn profile(&mut self, id: i32) -> Result<Option<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
profiles::dsl::profiles.find(id)
|
||||||
|
.first::<ModelProfile>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.optional()
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn active_profile(&mut self, user_id: i32) -> Result<Option<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
profiles::dsl::profiles
|
||||||
|
.filter(profiles::user_id.eq(user_id))
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.first::<ModelProfile>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.optional()
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn active_profile_by_discord_id(&mut self, discord_user_id: u64) -> Result<Option<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
let model_discord_user_id = discord_user_id as i64;
|
||||||
|
profiles::table
|
||||||
|
.inner_join(users::table)
|
||||||
|
.filter(users::discord_user_id.eq(model_discord_user_id))
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.select(ModelProfile::as_select())
|
||||||
|
.first(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.optional()
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn profiles_by_user_id(&mut self, user_id: i32) -> Result<Vec<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
let results: Vec<_> = profiles::dsl::profiles
|
||||||
|
.filter(profiles::user_id.eq(user_id))
|
||||||
|
.order(profiles::created_at.desc())
|
||||||
|
.get_results::<ModelProfile>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))?
|
||||||
|
.into_iter()
|
||||||
|
.map(Profile::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn profiles_by_discord_id(&mut self, discord_user_id: u64) -> Result<Vec<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
let model_discord_user_id = discord_user_id as i64;
|
||||||
|
let results = profiles::table
|
||||||
|
.inner_join(users::table)
|
||||||
|
.filter(users::discord_user_id.eq(model_discord_user_id))
|
||||||
|
.order(profiles::created_at.desc())
|
||||||
|
.select(ModelProfile::as_select())
|
||||||
|
.get_results(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))?
|
||||||
|
.into_iter()
|
||||||
|
.map(Profile::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_active_profile(&mut self, user_id: i32, profile_id: i32) -> Result<bool, RepositoryError<Self::BackendError>> {
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(move |conn| async move {
|
||||||
|
let num_affected = diesel::update(profiles::table.find(profile_id))
|
||||||
|
.set(profiles::is_active.eq(true))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if num_affected == 0 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::update(profiles::table)
|
||||||
|
.filter(profiles::user_id.eq(user_id))
|
||||||
|
.filter(profiles::id.ne(profile_id))
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.set(profiles::is_active.eq(false))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, AsChangeset)]
|
||||||
|
#[diesel(table_name = profiles)]
|
||||||
|
#[diesel(belongs_to(ModelUser))]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct ModelProfile {
|
||||||
|
pub id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
|
||||||
|
pub thumbnail_url: Option<String>,
|
||||||
|
pub image_url: Option<String>,
|
||||||
|
|
||||||
|
pub trainer_class: Option<String>,
|
||||||
|
pub nature: Option<String>,
|
||||||
|
pub partner_pokemon: Option<String>,
|
||||||
|
pub starting_region: Option<String>,
|
||||||
|
pub favourite_food: Option<String>,
|
||||||
|
pub likes: Option<String>,
|
||||||
|
pub quotes: Option<String>,
|
||||||
|
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModelProfile> for Profile {
|
||||||
|
fn from(value: ModelProfile) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
user_id: value.user_id,
|
||||||
|
|
||||||
|
thumbnail_url: value.thumbnail_url,
|
||||||
|
image_url: value.image_url,
|
||||||
|
|
||||||
|
trainer_class: value.trainer_class,
|
||||||
|
nature: value.nature,
|
||||||
|
partner_pokemon: value.partner_pokemon,
|
||||||
|
starting_region: value.starting_region,
|
||||||
|
favourite_food: value.favourite_food,
|
||||||
|
likes: value.likes,
|
||||||
|
quotes: value.quotes,
|
||||||
|
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
|
||||||
|
created_at: DateTime::from_naive_utc_and_offset(value.created_at, Utc),
|
||||||
|
is_active: value.is_active,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = profiles)]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct ModelNewProfile {
|
||||||
|
pub user_id: i32,
|
||||||
|
|
||||||
|
pub thumbnail_url: Option<String>,
|
||||||
|
pub image_url: Option<String>,
|
||||||
|
|
||||||
|
pub trainer_class: Option<String>,
|
||||||
|
pub nature: Option<String>,
|
||||||
|
pub partner_pokemon: Option<String>,
|
||||||
|
pub starting_region: Option<String>,
|
||||||
|
pub favourite_food: Option<String>,
|
||||||
|
pub likes: Option<String>,
|
||||||
|
pub quotes: Option<String>,
|
||||||
|
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NewProfile> for ModelNewProfile {
|
||||||
|
fn from(value: NewProfile) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: value.user_id,
|
||||||
|
|
||||||
|
thumbnail_url: value.thumbnail_url,
|
||||||
|
image_url: value.image_url,
|
||||||
|
|
||||||
|
trainer_class: value.trainer_class,
|
||||||
|
nature: value.nature,
|
||||||
|
partner_pokemon: value.partner_pokemon,
|
||||||
|
starting_region: value.starting_region,
|
||||||
|
favourite_food: value.favourite_food,
|
||||||
|
likes: value.likes,
|
||||||
|
quotes: value.quotes,
|
||||||
|
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
is_active: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,20 +81,6 @@ impl UserRepository for PostgresRepository<'_> {
|
|||||||
.map(|option| option.map(User::from))
|
.map(|option| option.map(User::from))
|
||||||
.map_err(|err| RepositoryError(BackendError::from(err)))
|
.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)]
|
#[derive(Queryable, Selectable, AsChangeset)]
|
||||||
@@ -104,9 +90,6 @@ impl UserRepository for PostgresRepository<'_> {
|
|||||||
struct ModelUser {
|
struct ModelUser {
|
||||||
id: i32,
|
id: i32,
|
||||||
discord_user_id: i64,
|
discord_user_id: i64,
|
||||||
pokemon_go_code: Option<String>,
|
|
||||||
pokemon_pocket_code: Option<String>,
|
|
||||||
switch_code: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ModelUser> for User {
|
impl From<ModelUser> for User {
|
||||||
@@ -114,9 +97,6 @@ impl From<ModelUser> for User {
|
|||||||
Self {
|
Self {
|
||||||
id: value.id,
|
id: value.id,
|
||||||
discord_user_id: value.discord_user_id as u64,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,9 +106,6 @@ impl From<User> for ModelUser {
|
|||||||
Self {
|
Self {
|
||||||
id: value.id,
|
id: value.id,
|
||||||
discord_user_id: value.discord_user_id as i64,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,18 +116,12 @@ impl From<User> for ModelUser {
|
|||||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
struct ModelNewUser {
|
struct ModelNewUser {
|
||||||
discord_user_id: i64,
|
discord_user_id: i64,
|
||||||
pokemon_go_code: Option<String>,
|
|
||||||
pokemon_pocket_code: Option<String>,
|
|
||||||
switch_code: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NewUser> for ModelNewUser {
|
impl From<NewUser> for ModelNewUser {
|
||||||
fn from(value: NewUser) -> Self {
|
fn from(value: NewUser) -> Self {
|
||||||
Self {
|
Self {
|
||||||
discord_user_id: value.discord_user_id as i64,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
// @generated automatically by Diesel CLI.
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
profiles (id) {
|
||||||
|
id -> Int4,
|
||||||
|
user_id -> Int4,
|
||||||
|
thumbnail_url -> Nullable<Text>,
|
||||||
|
image_url -> Nullable<Text>,
|
||||||
|
trainer_class -> Nullable<Text>,
|
||||||
|
nature -> Nullable<Text>,
|
||||||
|
partner_pokemon -> Nullable<Text>,
|
||||||
|
starting_region -> Nullable<Text>,
|
||||||
|
favourite_food -> Nullable<Text>,
|
||||||
|
likes -> Nullable<Text>,
|
||||||
|
quotes -> Nullable<Text>,
|
||||||
|
#[max_length = 32]
|
||||||
|
pokemon_go_code -> Nullable<Varchar>,
|
||||||
|
#[max_length = 32]
|
||||||
|
pokemon_pocket_code -> Nullable<Varchar>,
|
||||||
|
#[max_length = 32]
|
||||||
|
switch_code -> Nullable<Varchar>,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
is_active -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
staff_roles (id) {
|
staff_roles (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
@@ -11,16 +35,13 @@ diesel::table! {
|
|||||||
users (id) {
|
users (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
discord_user_id -> Int8,
|
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>,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::joinable!(profiles -> users (user_id));
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
profiles,
|
||||||
staff_roles,
|
staff_roles,
|
||||||
users,
|
users,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use cipher_core::repository::RepositoryProvider;
|
|||||||
|
|
||||||
use crate::BackendError;
|
use crate::BackendError;
|
||||||
|
|
||||||
|
mod profile_repository;
|
||||||
mod staff_role_repository;
|
mod staff_role_repository;
|
||||||
mod user_repository;
|
mod user_repository;
|
||||||
|
|
||||||
|
|||||||
241
cipher_database/src/sqlite/repository/profile_repository.rs
Normal file
241
cipher_database/src/sqlite/repository/profile_repository.rs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
use chrono::DateTime;
|
||||||
|
use chrono::NaiveDateTime;
|
||||||
|
use chrono::Utc;
|
||||||
|
use cipher_core::repository::profile_repository::NewProfile;
|
||||||
|
use diesel_async::scoped_futures::ScopedFutureExt;
|
||||||
|
use diesel_async::AsyncConnection;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use cipher_core::repository::profile_repository::Profile;
|
||||||
|
use cipher_core::repository::profile_repository::ProfileRepository;
|
||||||
|
use cipher_core::repository::RepositoryError;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
use crate::sqlite::schema::profiles;
|
||||||
|
use crate::sqlite::schema::users;
|
||||||
|
use crate::BackendError;
|
||||||
|
|
||||||
|
use super::SqliteRepository;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ProfileRepository for SqliteRepository<'_> {
|
||||||
|
type BackendError = BackendError;
|
||||||
|
|
||||||
|
async fn insert_profile(&mut self, new_profile: NewProfile) -> Result<Profile, RepositoryError<Self::BackendError>> {
|
||||||
|
let model_new_profile = ModelNewProfile::from(new_profile);
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(|conn| async move {
|
||||||
|
diesel::update(profiles::table)
|
||||||
|
.filter(profiles::user_id.eq(model_new_profile.user_id))
|
||||||
|
.set(profiles::is_active.eq(false))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
diesel::insert_into(profiles::table)
|
||||||
|
.values(&model_new_profile)
|
||||||
|
.returning(ModelProfile::as_returning())
|
||||||
|
.get_result(conn)
|
||||||
|
.await
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn profile(&mut self, id: i32) -> Result<Option<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
profiles::dsl::profiles.find(id)
|
||||||
|
.first::<ModelProfile>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.optional()
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn active_profile(&mut self, user_id: i32) -> Result<Option<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
profiles::dsl::profiles
|
||||||
|
.filter(profiles::user_id.eq(user_id))
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.first::<ModelProfile>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.optional()
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn active_profile_by_discord_id(&mut self, discord_user_id: u64) -> Result<Option<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
let model_discord_user_id = discord_user_id as i64;
|
||||||
|
profiles::table
|
||||||
|
.inner_join(users::table)
|
||||||
|
.filter(users::discord_user_id.eq(model_discord_user_id))
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.select(ModelProfile::as_select())
|
||||||
|
.first(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map(Profile::from)
|
||||||
|
.optional()
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn profiles_by_user_id(&mut self, user_id: i32) -> Result<Vec<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
let results: Vec<_> = profiles::dsl::profiles
|
||||||
|
.filter(profiles::user_id.eq(user_id))
|
||||||
|
.order(profiles::created_at.desc())
|
||||||
|
.get_results::<ModelProfile>(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))?
|
||||||
|
.into_iter()
|
||||||
|
.map(Profile::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn profiles_by_discord_id(&mut self, discord_user_id: u64) -> Result<Vec<Profile>, RepositoryError<Self::BackendError>> {
|
||||||
|
let model_discord_user_id = discord_user_id as i64;
|
||||||
|
let results = profiles::table
|
||||||
|
.inner_join(users::table)
|
||||||
|
.filter(users::discord_user_id.eq(model_discord_user_id))
|
||||||
|
.order(profiles::created_at.desc())
|
||||||
|
.select(ModelProfile::as_select())
|
||||||
|
.get_results(&mut self.conn)
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))?
|
||||||
|
.into_iter()
|
||||||
|
.map(Profile::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_active_profile(&mut self, user_id: i32, profile_id: i32) -> Result<bool, RepositoryError<Self::BackendError>> {
|
||||||
|
self.conn
|
||||||
|
.transaction::<_, diesel::result::Error, _>(move |conn| async move {
|
||||||
|
let num_affected = diesel::update(profiles::table.find(profile_id))
|
||||||
|
.set(profiles::is_active.eq(true))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if num_affected == 0 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::update(profiles::table)
|
||||||
|
.filter(profiles::user_id.eq(user_id))
|
||||||
|
.filter(profiles::id.ne(profile_id))
|
||||||
|
.filter(profiles::is_active.eq(true))
|
||||||
|
.set(profiles::is_active.eq(false))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}.scope_boxed())
|
||||||
|
.await
|
||||||
|
.map_err(|err| RepositoryError(BackendError::from(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, AsChangeset)]
|
||||||
|
#[diesel(table_name = profiles)]
|
||||||
|
#[diesel(belongs_to(ModelUser))]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||||
|
pub struct ModelProfile {
|
||||||
|
pub id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
|
||||||
|
pub thumbnail_url: Option<String>,
|
||||||
|
pub image_url: Option<String>,
|
||||||
|
|
||||||
|
pub trainer_class: Option<String>,
|
||||||
|
pub nature: Option<String>,
|
||||||
|
pub partner_pokemon: Option<String>,
|
||||||
|
pub starting_region: Option<String>,
|
||||||
|
pub favourite_food: Option<String>,
|
||||||
|
pub likes: Option<String>,
|
||||||
|
pub quotes: Option<String>,
|
||||||
|
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModelProfile> for Profile {
|
||||||
|
fn from(value: ModelProfile) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
user_id: value.user_id,
|
||||||
|
|
||||||
|
thumbnail_url: value.thumbnail_url,
|
||||||
|
image_url: value.image_url,
|
||||||
|
|
||||||
|
trainer_class: value.trainer_class,
|
||||||
|
nature: value.nature,
|
||||||
|
partner_pokemon: value.partner_pokemon,
|
||||||
|
starting_region: value.starting_region,
|
||||||
|
favourite_food: value.favourite_food,
|
||||||
|
likes: value.likes,
|
||||||
|
quotes: value.quotes,
|
||||||
|
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
|
||||||
|
created_at: DateTime::from_naive_utc_and_offset(value.created_at, Utc),
|
||||||
|
is_active: value.is_active,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[diesel(table_name = profiles)]
|
||||||
|
#[diesel(treat_none_as_null = true)]
|
||||||
|
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||||
|
pub struct ModelNewProfile {
|
||||||
|
pub user_id: i32,
|
||||||
|
|
||||||
|
pub thumbnail_url: Option<String>,
|
||||||
|
pub image_url: Option<String>,
|
||||||
|
|
||||||
|
pub trainer_class: Option<String>,
|
||||||
|
pub nature: Option<String>,
|
||||||
|
pub partner_pokemon: Option<String>,
|
||||||
|
pub starting_region: Option<String>,
|
||||||
|
pub favourite_food: Option<String>,
|
||||||
|
pub likes: Option<String>,
|
||||||
|
pub quotes: Option<String>,
|
||||||
|
|
||||||
|
pub pokemon_go_code: Option<String>,
|
||||||
|
pub pokemon_pocket_code: Option<String>,
|
||||||
|
pub switch_code: Option<String>,
|
||||||
|
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
pub is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NewProfile> for ModelNewProfile {
|
||||||
|
fn from(value: NewProfile) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: value.user_id,
|
||||||
|
|
||||||
|
thumbnail_url: value.thumbnail_url,
|
||||||
|
image_url: value.image_url,
|
||||||
|
|
||||||
|
trainer_class: value.trainer_class,
|
||||||
|
nature: value.nature,
|
||||||
|
partner_pokemon: value.partner_pokemon,
|
||||||
|
starting_region: value.starting_region,
|
||||||
|
favourite_food: value.favourite_food,
|
||||||
|
likes: value.likes,
|
||||||
|
quotes: value.quotes,
|
||||||
|
|
||||||
|
pokemon_go_code: value.pokemon_go_code,
|
||||||
|
pokemon_pocket_code: value.pokemon_pocket_code,
|
||||||
|
switch_code: value.switch_code,
|
||||||
|
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
is_active: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,20 +81,6 @@ impl UserRepository for SqliteRepository<'_> {
|
|||||||
.map(|option| option.map(User::from))
|
.map(|option| option.map(User::from))
|
||||||
.map_err(|err| RepositoryError(BackendError::from(err)))
|
.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)]
|
#[derive(Queryable, Selectable, AsChangeset)]
|
||||||
@@ -104,9 +90,6 @@ impl UserRepository for SqliteRepository<'_> {
|
|||||||
struct ModelUser {
|
struct ModelUser {
|
||||||
id: i32,
|
id: i32,
|
||||||
discord_user_id: i64,
|
discord_user_id: i64,
|
||||||
pokemon_go_code: Option<String>,
|
|
||||||
pokemon_pocket_code: Option<String>,
|
|
||||||
switch_code: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ModelUser> for User {
|
impl From<ModelUser> for User {
|
||||||
@@ -114,9 +97,6 @@ impl From<ModelUser> for User {
|
|||||||
Self {
|
Self {
|
||||||
id: value.id,
|
id: value.id,
|
||||||
discord_user_id: value.discord_user_id as u64,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,9 +106,6 @@ impl From<User> for ModelUser {
|
|||||||
Self {
|
Self {
|
||||||
id: value.id,
|
id: value.id,
|
||||||
discord_user_id: value.discord_user_id as i64,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,18 +116,12 @@ impl From<User> for ModelUser {
|
|||||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||||
struct ModelNewUser {
|
struct ModelNewUser {
|
||||||
discord_user_id: i64,
|
discord_user_id: i64,
|
||||||
pokemon_go_code: Option<String>,
|
|
||||||
pokemon_pocket_code: Option<String>,
|
|
||||||
switch_code: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NewUser> for ModelNewUser {
|
impl From<NewUser> for ModelNewUser {
|
||||||
fn from(value: NewUser) -> Self {
|
fn from(value: NewUser) -> Self {
|
||||||
Self {
|
Self {
|
||||||
discord_user_id: value.discord_user_id as i64,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
// @generated automatically by Diesel CLI.
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
profiles (id) {
|
||||||
|
id -> Integer,
|
||||||
|
user_id -> Integer,
|
||||||
|
thumbnail_url -> Nullable<Text>,
|
||||||
|
image_url -> Nullable<Text>,
|
||||||
|
trainer_class -> Nullable<Text>,
|
||||||
|
nature -> Nullable<Text>,
|
||||||
|
partner_pokemon -> Nullable<Text>,
|
||||||
|
starting_region -> Nullable<Text>,
|
||||||
|
favourite_food -> Nullable<Text>,
|
||||||
|
likes -> Nullable<Text>,
|
||||||
|
quotes -> Nullable<Text>,
|
||||||
|
pokemon_go_code -> Nullable<Text>,
|
||||||
|
pokemon_pocket_code -> Nullable<Text>,
|
||||||
|
switch_code -> Nullable<Text>,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
is_active -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
staff_roles (id) {
|
staff_roles (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
@@ -11,13 +32,13 @@ diesel::table! {
|
|||||||
users (id) {
|
users (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
discord_user_id -> BigInt,
|
discord_user_id -> BigInt,
|
||||||
pokemon_go_code -> Nullable<Text>,
|
|
||||||
pokemon_pocket_code -> Nullable<Text>,
|
|
||||||
switch_code -> Nullable<Text>,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::joinable!(profiles -> users (user_id));
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
profiles,
|
||||||
staff_roles,
|
staff_roles,
|
||||||
users,
|
users,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
use cipher_core::repository::profile_repository::NewProfile;
|
||||||
|
use cipher_core::repository::profile_repository::ProfileRepository;
|
||||||
use cipher_core::repository::user_repository::NewUser;
|
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::user_repository::UserRepository;
|
||||||
use cipher_core::repository::RepositoryError;
|
use cipher_core::repository::RepositoryError;
|
||||||
use cipher_core::repository::RepositoryProvider;
|
use cipher_core::repository::RepositoryProvider;
|
||||||
@@ -176,11 +177,16 @@ where
|
|||||||
{
|
{
|
||||||
let mut repo = ctx.data.repository().await?;
|
let mut repo = ctx.data.repository().await?;
|
||||||
|
|
||||||
if let Some(mut user) = repo.user_by_discord_user_id(discord_user_id).await? {
|
let user = match repo.user_by_discord_user_id(discord_user_id).await? {
|
||||||
|
Some(user) => user,
|
||||||
|
None => repo.insert_user(NewUser { discord_user_id }).await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mut profile) = repo.active_profile_by_discord_id(discord_user_id).await? {
|
||||||
let defaults = EditCodesModal {
|
let defaults = EditCodesModal {
|
||||||
pokemon_go_code: user.pokemon_go_code.clone(),
|
pokemon_go_code: profile.pokemon_go_code.clone(),
|
||||||
pokemon_pocket_code: user.pokemon_pocket_code.clone(),
|
pokemon_pocket_code: profile.pokemon_pocket_code.clone(),
|
||||||
switch_code: user.switch_code.clone(),
|
switch_code: profile.switch_code.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut data = match EditCodesModal::execute_with_defaults(ctx, defaults).await? {
|
let mut data = match EditCodesModal::execute_with_defaults(ctx, defaults).await? {
|
||||||
@@ -190,15 +196,11 @@ where
|
|||||||
|
|
||||||
data.validate().map_err(EditError::ValidationError)?;
|
data.validate().map_err(EditError::ValidationError)?;
|
||||||
|
|
||||||
user = User {
|
profile.pokemon_go_code = data.pokemon_go_code;
|
||||||
id: user.id,
|
profile.pokemon_pocket_code = data.pokemon_pocket_code;
|
||||||
discord_user_id: user.discord_user_id,
|
profile.switch_code = data.switch_code;
|
||||||
pokemon_go_code: data.pokemon_go_code,
|
|
||||||
pokemon_pocket_code: data.pokemon_pocket_code,
|
|
||||||
switch_code: data.switch_code,
|
|
||||||
};
|
|
||||||
|
|
||||||
repo.update_user(user).await?;
|
repo.insert_profile(profile.into_new()).await?;
|
||||||
} else {
|
} else {
|
||||||
let mut data = match EditCodesModal::execute(ctx).await? {
|
let mut data = match EditCodesModal::execute(ctx).await? {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
@@ -207,14 +209,26 @@ where
|
|||||||
|
|
||||||
data.validate().map_err(EditError::ValidationError)?;
|
data.validate().map_err(EditError::ValidationError)?;
|
||||||
|
|
||||||
let new_user = NewUser {
|
let new_profile = NewProfile {
|
||||||
discord_user_id,
|
user_id: user.id,
|
||||||
|
|
||||||
|
thumbnail_url: None,
|
||||||
|
image_url: None,
|
||||||
|
|
||||||
|
trainer_class: None,
|
||||||
|
nature: None,
|
||||||
|
partner_pokemon: None,
|
||||||
|
starting_region: None,
|
||||||
|
favourite_food: None,
|
||||||
|
likes: None,
|
||||||
|
quotes: None,
|
||||||
|
|
||||||
pokemon_go_code: data.pokemon_go_code,
|
pokemon_go_code: data.pokemon_go_code,
|
||||||
pokemon_pocket_code: data.pokemon_pocket_code,
|
pokemon_pocket_code: data.pokemon_pocket_code,
|
||||||
switch_code: data.switch_code,
|
switch_code: data.switch_code,
|
||||||
};
|
};
|
||||||
|
|
||||||
repo.insert_user(new_user).await?;
|
repo.insert_profile(new_profile).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use cipher_core::repository::user_repository::UserRepository;
|
use cipher_core::repository::profile_repository::ProfileRepository;
|
||||||
use cipher_core::repository::RepositoryProvider;
|
use cipher_core::repository::RepositoryProvider;
|
||||||
use poise::CreateReply;
|
use poise::CreateReply;
|
||||||
use serenity::all::CreateEmbed;
|
use serenity::all::CreateEmbed;
|
||||||
@@ -66,8 +66,6 @@ async fn show_inner<R: RepositoryProvider + Send + Sync>(
|
|||||||
member: Member,
|
member: Member,
|
||||||
ephemeral: bool,
|
ephemeral: bool,
|
||||||
) -> Result<(), AppError<R::BackendError>> {
|
) -> Result<(), AppError<R::BackendError>> {
|
||||||
let mut repo = ctx.data.repository().await?;
|
|
||||||
|
|
||||||
let avatar_url = crate::utils::member_avatar_url(&member);
|
let avatar_url = crate::utils::member_avatar_url(&member);
|
||||||
|
|
||||||
let embed_color = match member.colour(ctx) {
|
let embed_color = match member.colour(ctx) {
|
||||||
@@ -82,18 +80,19 @@ async fn show_inner<R: RepositoryProvider + Send + Sync>(
|
|||||||
|
|
||||||
let mut is_profile_empty = true;
|
let mut is_profile_empty = true;
|
||||||
|
|
||||||
if let Some(user_info) = repo.user_by_discord_user_id(member.user.id.get()).await? {
|
let mut repo = ctx.data.repository().await?;
|
||||||
if let Some(code) = user_info.pokemon_go_code {
|
if let Some(profile) = repo.active_profile_by_discord_id(member.user.id.get()).await? {
|
||||||
|
if let Some(code) = profile.pokemon_go_code {
|
||||||
embed = embed.field("Pokémon Go Friend Code", code, false);
|
embed = embed.field("Pokémon Go Friend Code", code, false);
|
||||||
is_profile_empty = false;
|
is_profile_empty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(code) = user_info.pokemon_pocket_code {
|
if let Some(code) = profile.pokemon_pocket_code {
|
||||||
embed = embed.field("Pokémon TCG Pocket Friend Code", code, false);
|
embed = embed.field("Pokémon TCG Pocket Friend Code", code, false);
|
||||||
is_profile_empty = false;
|
is_profile_empty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(code) = user_info.switch_code {
|
if let Some(code) = profile.switch_code {
|
||||||
embed = embed.field("Nintendo Switch Friend Code", code, false);
|
embed = embed.field("Nintendo Switch Friend Code", code, false);
|
||||||
is_profile_empty = false;
|
is_profile_empty = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user