This commit is contained in:
root
2025-12-14 10:39:18 +03:00
commit 639f4e2b4e
179 changed files with 21065 additions and 0 deletions

View File

@@ -0,0 +1 @@
DATABASE_URL=postgres://dollhouse_user:hahahadollhouse@localhost:5432/dollhouse_db

View File

@@ -0,0 +1,16 @@
[package]
name = "dollhouse-db"
version = "0.1.0"
edition = "2024"
[dependencies]
bb8 = "0.9.0"
chrono = "0.4.42"
diesel = { version = "2.2.0", features = ["postgres", "chrono", "uuid"] }
diesel-async = { version = "0.7.4", features = ["postgres", "pool", "bb8"] }
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
diesel_migrations = "2.3.0"
r2d2 = "0.8.10"
thiserror = { workspace = true }
uuid = { workspace = true }
dotenv = "0.15"

View 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/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
[migrations_directory]
dir = "migrations"

View File

View 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();

View File

@@ -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;

View File

@@ -0,0 +1,11 @@
-- This file should undo anything in `up.sql`
DROP TABLE IF EXISTS corps_replicants;
DROP TABLE IF EXISTS replicants_stats;
DROP TABLE IF EXISTS replicants;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS corps;
DROP TYPE IF EXISTS replicant_gender;
DROP TYPE IF EXISTS replicant_status;
DROP TYPE IF EXISTS user_role;

View File

@@ -0,0 +1,44 @@
-- Your SQL goes here
CREATE TYPE user_role AS ENUM ('corp_admin', 'user');
CREATE TYPE replicant_status AS ENUM ('active', 'decommissioned');
CREATE TYPE replicant_gender AS ENUM ('male', 'female', 'non-binary');
CREATE TABLE IF NOT EXISTS corps (
id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
invite_code VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role user_role NOT NULL DEFAULT 'user',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
corp_id UUID REFERENCES corps(id)
);
CREATE TABLE IF NOT EXISTS replicants (
id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
status replicant_status NOT NULL DEFAULT 'active',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
gender replicant_gender NOT NULL,
corp_id UUID REFERENCES corps(id) NOT NULL,
is_private BOOLEAN NOT NULL DEFAULT TRUE,
firmware_file VARCHAR(255)
);
CREATE TABLE IF NOT EXISTS replicants_stats (
replicant_id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid() REFERENCES replicants(id),
health INTEGER NOT NULL DEFAULT 100,
strength INTEGER NOT NULL DEFAULT 100,
intelligence INTEGER NOT NULL DEFAULT 100,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_replicant_stats_replicant_id ON replicants_stats(replicant_id);
CREATE INDEX idx_replicants_status ON replicants(status);

View File

@@ -0,0 +1,20 @@
ALTER TABLE replicants_stats
DROP CONSTRAINT IF EXISTS replicants_stats_replicant_id_fkey;
ALTER TABLE replicants_stats
ADD CONSTRAINT replicants_stats_replicant_id_fkey
FOREIGN KEY (replicant_id) REFERENCES replicants(id);
ALTER TABLE replicants
DROP CONSTRAINT IF EXISTS replicants_corp_id_fkey;
ALTER TABLE replicants
ADD CONSTRAINT replicants_corp_id_fkey
FOREIGN KEY (corp_id) REFERENCES corps(id);
ALTER TABLE users
DROP CONSTRAINT IF EXISTS users_corp_id_fkey;
ALTER TABLE users
ADD CONSTRAINT users_corp_id_fkey
FOREIGN KEY (corp_id) REFERENCES corps(id);

View File

@@ -0,0 +1,20 @@
ALTER TABLE users
DROP CONSTRAINT IF EXISTS users_corp_id_fkey;
ALTER TABLE users
ADD CONSTRAINT users_corp_id_fkey
FOREIGN KEY (corp_id) REFERENCES corps(id) ON DELETE CASCADE;
ALTER TABLE replicants
DROP CONSTRAINT IF EXISTS replicants_corp_id_fkey;
ALTER TABLE replicants
ADD CONSTRAINT replicants_corp_id_fkey
FOREIGN KEY (corp_id) REFERENCES corps(id) ON DELETE CASCADE;
ALTER TABLE replicants_stats
DROP CONSTRAINT IF EXISTS replicants_stats_replicant_id_fkey;
ALTER TABLE replicants_stats
ADD CONSTRAINT replicants_stats_replicant_id_fkey
FOREIGN KEY (replicant_id) REFERENCES replicants(id) ON DELETE CASCADE;

View File

@@ -0,0 +1,35 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DbError {
#[error("Database query error")]
QueryError,
#[error("Not found")]
NotFound,
#[error("Already exists")]
AlreadyExists,
#[error("Unique constraint violation")]
UniqueViolation,
#[error("Foreign key violation")]
ForeignKeyViolation,
}
impl From<diesel::result::Error> for DbError {
fn from(err: diesel::result::Error) -> Self {
match err {
diesel::result::Error::NotFound => DbError::NotFound,
diesel::result::Error::DatabaseError(kind, _) => match kind {
diesel::result::DatabaseErrorKind::UniqueViolation => DbError::UniqueViolation,
diesel::result::DatabaseErrorKind::ForeignKeyViolation => {
DbError::ForeignKeyViolation
}
_ => DbError::QueryError,
},
_ => DbError::QueryError,
}
}
}

View File

@@ -0,0 +1,33 @@
pub mod errors;
mod models;
pub mod repositories;
mod schema;
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
use diesel_migrations::{EmbeddedMigrations, embed_migrations};
pub use models::{NewCorp, NewReplicant, NewUser, ReplicantGender, ReplicantStatus, UserRole};
use std::env;
pub use diesel_async::AsyncPgConnection;
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
pub type Pool = bb8::Pool<AsyncDieselConnectionManager<AsyncPgConnection>>;
fn database_url() -> String {
#[cfg(debug_assertions)]
{
dotenv::dotenv().ok();
}
env::var("DATABASE_URL").expect("DATABASE_URL must be set")
}
pub async fn create_db_pool() -> Pool {
let database_url = database_url();
let config = AsyncDieselConnectionManager::<AsyncPgConnection>::new(database_url);
Pool::builder()
.build(config)
.await
.expect("Failed to create pool")
}

View File

@@ -0,0 +1,175 @@
use crate::schema::users;
use chrono::NaiveDateTime;
use diesel::prelude::*;
use std::convert::TryFrom;
use uuid::Uuid;
#[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq)]
#[ExistingTypePath = "crate::schema::sql_types::ReplicantStatus"]
pub enum ReplicantStatus {
#[db_rename = "active"]
Active,
#[db_rename = "decommissioned"]
Decommissioned,
}
#[derive(diesel_derive_enum::DbEnum, Debug, Clone, PartialEq)]
#[ExistingTypePath = "crate::schema::sql_types::ReplicantGender"]
pub enum ReplicantGender {
#[db_rename = "male"]
Male,
#[db_rename = "female"]
Female,
#[db_rename = "non-binary"]
NonBinary,
}
#[derive(Debug, PartialEq, Clone, diesel_derive_enum::DbEnum)]
#[ExistingTypePath = "crate::schema::sql_types::UserRole"]
pub enum UserRole {
#[db_rename = "corp_admin"]
CorpAdmin,
#[db_rename = "user"]
User,
}
#[derive(Insertable, AsChangeset)]
#[diesel(table_name = crate::schema::users)]
pub struct NewUser {
pub username: String,
pub password: String,
}
#[derive(Queryable, Selectable)]
#[diesel(belongs_to(Corp))]
pub struct User {
pub id: Uuid,
pub username: String,
pub password: String,
pub role: UserRole,
pub created_at: NaiveDateTime,
pub corp_id: Option<Uuid>,
}
#[derive(Insertable, AsChangeset)]
#[diesel(table_name = crate::schema::corps)]
pub struct NewCorp {
pub id: Uuid,
pub name: String,
pub description: String,
pub invite_code: String,
}
#[derive(Queryable, Selectable, Debug)]
#[diesel(table_name = crate::schema::corps)]
#[diesel(check_for_backend(diesel::pg::Pg))]
#[diesel(belongs_to(User))]
pub struct Corp {
pub id: Uuid,
pub name: String,
pub description: String,
pub created_at: NaiveDateTime,
pub invite_code: String,
}
#[derive(Insertable, AsChangeset)]
#[diesel(table_name = crate::schema::replicants)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct NewReplicant {
pub name: String,
pub description: String,
pub status: ReplicantStatus,
pub gender: ReplicantGender,
pub corp_id: Uuid,
}
#[derive(Insertable, AsChangeset)]
#[diesel(table_name = crate::schema::replicants_stats)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct NewReplicantStats {
pub replicant_id: Uuid,
pub health: i32,
pub strength: i32,
pub intelligence: i32,
}
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::replicants)]
#[diesel(check_for_backend(diesel::pg::Pg))]
#[diesel(belongs_to(Corp))]
pub struct Replicant {
pub id: Uuid,
pub name: String,
pub description: String,
pub status: ReplicantStatus,
pub created_at: NaiveDateTime,
pub gender: ReplicantGender,
pub corp_id: Uuid,
pub is_private: bool,
pub firmware_file: Option<String>,
}
#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::replicants_stats)]
#[diesel(check_for_backend(diesel::pg::Pg))]
#[diesel(belongs_to(Replicant))]
pub struct ReplicantStats {
pub replicant_id: Uuid,
pub health: i32,
pub strength: i32,
pub intelligence: i32,
pub created_at: NaiveDateTime,
}
#[derive(Debug)]
pub struct ReplicantFull {
pub id: Uuid,
pub name: String,
pub description: String,
pub gender: ReplicantGender,
pub status: ReplicantStatus,
pub created_at: NaiveDateTime,
pub firmware_file: Option<String>,
pub is_private: bool,
pub corp_id: Uuid,
pub health: i32,
pub strength: i32,
pub intelligence: i32,
}
impl TryFrom<String> for ReplicantStatus {
type Error = &'static str;
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"active" => Ok(ReplicantStatus::Active),
"decommissioned" => Ok(ReplicantStatus::Decommissioned),
_ => Err("Invalid status"),
}
}
}
impl TryFrom<String> for ReplicantGender {
type Error = &'static str;
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"male" => Ok(ReplicantGender::Male),
"female" => Ok(ReplicantGender::Female),
"non_binary" => Ok(ReplicantGender::NonBinary),
_ => Err("Invalid gender"),
}
}
}
impl TryFrom<String> for UserRole {
type Error = &'static str;
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"corp_admin" => Ok(UserRole::CorpAdmin),
"user" => Ok(UserRole::User),
_ => Err("Invalid role"),
}
}
}

View File

@@ -0,0 +1,173 @@
use crate::errors::DbError;
use crate::models::{Corp, NewCorp, UserRole};
use diesel::prelude::*;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use uuid::Uuid;
pub struct CorpRepository;
impl CorpRepository {
pub async fn create(conn: &mut AsyncPgConnection, new_corp: NewCorp) -> Result<Corp, DbError> {
use crate::schema::corps::dsl::*;
diesel::insert_into(corps)
.values(&new_corp)
.get_result(conn)
.await
.map_err(Into::into)
}
pub async fn get_corp(conn: &mut AsyncPgConnection, corp_id: Uuid) -> Result<Corp, DbError> {
use crate::schema::corps::dsl::*;
corps
.find(corp_id)
.select(Corp::as_select())
.first(conn)
.await
.map_err(Into::into)
}
pub async fn get_corps(
conn: &mut AsyncPgConnection,
limit: usize,
offset: usize,
) -> Result<Vec<Corp>, DbError> {
use crate::schema::corps::dsl::*;
corps
.select(Corp::as_select())
.limit(limit as i64)
.offset(offset as i64)
.get_results(conn)
.await
.map_err(Into::into)
}
pub async fn get_corps_by_admin_id(
conn: &mut AsyncPgConnection,
admin_id: Uuid,
) -> Result<Vec<Corp>, DbError> {
use crate::schema::{corps, users};
corps::table
.inner_join(users::table.on(users::corp_id.eq(corps::id.nullable())))
.filter(users::id.eq(admin_id))
.filter(users::role.eq(UserRole::CorpAdmin))
.select(corps::all_columns)
.get_results(conn)
.await
.map_err(Into::into)
}
pub async fn get_corp_by_user(
conn: &mut AsyncPgConnection,
user_id: Uuid,
) -> Result<Option<Corp>, DbError> {
use crate::schema::{corps, users};
corps::table
.inner_join(users::table.on(users::corp_id.eq(corps::id.nullable())))
.filter(users::id.eq(user_id))
.select(corps::all_columns)
.first(conn)
.await
.optional()
.map_err(Into::into)
}
pub async fn get_corp_user_ids_with_names(
conn: &mut AsyncPgConnection,
c_id: Uuid,
) -> Result<Vec<(Uuid, String, UserRole)>, DbError> {
use crate::schema::users::dsl::*;
users
.filter(corp_id.eq(c_id))
.select((id, username, role))
.load(conn)
.await
.map_err(Into::into)
}
pub async fn get_corp_user_ids(
conn: &mut AsyncPgConnection,
c_id: Uuid,
) -> Result<Vec<Uuid>, DbError> {
use crate::schema::users::dsl::*;
users
.filter(corp_id.eq(c_id))
.select(id)
.load(conn)
.await
.map_err(Into::into)
}
pub async fn get_corp_user_count(
conn: &mut AsyncPgConnection,
c_id: Uuid,
) -> Result<i64, DbError> {
use crate::schema::users::dsl::*;
users
.filter(corp_id.eq(c_id))
.count()
.get_result(conn)
.await
.map_err(Into::into)
}
pub async fn join_by_invite(
conn: &mut AsyncPgConnection,
user_id: Uuid,
invite_code: &str,
) -> Result<(), DbError> {
use crate::schema::{corps, users};
let user_exists = users::table
.find(user_id)
.select(users::id)
.first::<Uuid>(conn)
.await
.optional()?;
if user_exists.is_none() {
return Err(DbError::NotFound);
}
let current_corp = users::table
.find(user_id)
.select(users::corp_id)
.first::<Option<Uuid>>(conn)
.await?;
if current_corp.is_some() {
return Err(DbError::UniqueViolation);
}
let corp = corps::table
.filter(corps::invite_code.eq(invite_code))
.first::<Corp>(conn)
.await?;
diesel::update(users::table.find(user_id))
.set(users::corp_id.eq(corp.id))
.execute(conn)
.await?;
Ok(())
}
pub async fn find_by_invite_code(
conn: &mut AsyncPgConnection,
code: &str,
) -> Result<Corp, DbError> {
use crate::schema::corps::dsl::*;
corps
.filter(invite_code.eq(code))
.first(conn)
.await
.map_err(Into::into)
}
}

View File

@@ -0,0 +1,7 @@
pub mod corp;
pub mod replicant;
pub mod user;
pub use corp::CorpRepository;
pub use replicant::ReplicantRepository;
pub use user::UserRepository;

View File

@@ -0,0 +1,334 @@
use crate::errors::DbError;
use crate::models::{
NewReplicant, NewReplicantStats, Replicant, ReplicantFull, ReplicantStats, ReplicantStatus,
};
use diesel::prelude::*;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use uuid::Uuid;
pub struct ReplicantRepository;
impl ReplicantRepository {
pub async fn create(
conn: &mut AsyncPgConnection,
new_replicant: NewReplicant,
) -> Result<ReplicantFull, DbError> {
use crate::schema::{replicants, replicants_stats};
let replicant = diesel::insert_into(replicants::table)
.values(&new_replicant)
.get_result::<Replicant>(conn)
.await?;
let stats = NewReplicantStats {
replicant_id: replicant.id,
health: 100,
strength: 100,
intelligence: 100,
};
let stats = diesel::insert_into(replicants_stats::table)
.values(&stats)
.get_result::<ReplicantStats>(conn)
.await?;
Ok(ReplicantFull {
id: replicant.id,
name: replicant.name,
description: replicant.description,
gender: replicant.gender,
status: replicant.status,
created_at: replicant.created_at,
is_private: replicant.is_private,
firmware_file: replicant.firmware_file,
corp_id: replicant.corp_id,
health: stats.health,
strength: stats.strength,
intelligence: stats.intelligence,
})
}
pub async fn get(
conn: &mut AsyncPgConnection,
replicant_id: Uuid,
) -> Result<Replicant, DbError> {
use crate::schema::replicants::dsl::*;
replicants
.find(replicant_id)
.first(conn)
.await
.map_err(Into::into)
}
pub async fn get_optional(
conn: &mut AsyncPgConnection,
replicant_id: Uuid,
) -> Result<Option<Replicant>, DbError> {
use crate::schema::replicants::dsl::*;
replicants
.find(replicant_id)
.first(conn)
.await
.optional()
.map_err(Into::into)
}
pub async fn get_much(
conn: &mut AsyncPgConnection,
limit: usize,
offset: usize,
) -> Result<Vec<ReplicantFull>, DbError> {
use crate::schema::{replicants, replicants_stats};
let results = replicants::table
.inner_join(replicants_stats::table)
.filter(replicants::is_private.eq(false))
.select((Replicant::as_select(), ReplicantStats::as_select()))
.limit(limit as i64)
.offset(offset as i64)
.load::<(Replicant, ReplicantStats)>(conn)
.await?;
Ok(results
.into_iter()
.map(|(rep, stats)| ReplicantFull {
id: rep.id,
name: rep.name,
description: rep.description,
gender: rep.gender,
status: rep.status,
created_at: rep.created_at,
is_private: rep.is_private,
firmware_file: rep.firmware_file,
corp_id: rep.corp_id,
health: stats.health,
strength: stats.strength,
intelligence: stats.intelligence,
})
.collect())
}
pub async fn apply_mission_damage(
conn: &mut AsyncPgConnection,
replicant_id: Uuid,
damage: i32,
) -> Result<(i32, ReplicantStatus), DbError> {
use crate::schema::{replicants, replicants_stats};
let (current_health, current_status): (i32, ReplicantStatus) = replicants::table
.inner_join(replicants_stats::table)
.filter(replicants::id.eq(replicant_id))
.select((replicants_stats::health, replicants::status))
.first(conn)
.await?;
let new_health = (current_health - damage).max(0);
diesel::update(replicants_stats::table.find(replicant_id))
.set(replicants_stats::health.eq(new_health))
.execute(conn)
.await?;
let mut new_status = current_status.clone();
if new_health <= 0 && current_status != ReplicantStatus::Decommissioned {
diesel::update(replicants::table.find(replicant_id))
.set(replicants::status.eq(ReplicantStatus::Decommissioned))
.execute(conn)
.await?;
new_status = ReplicantStatus::Decommissioned;
}
Ok((new_health, new_status))
}
pub async fn get_firmware(
conn: &mut AsyncPgConnection,
replicant_id: Uuid,
) -> Result<Option<String>, DbError> {
use crate::schema::replicants::dsl::*;
replicants
.filter(id.eq(replicant_id))
.select(firmware_file)
.first(conn)
.await
.map_err(Into::into)
}
pub async fn get_corp_replicants(
conn: &mut AsyncPgConnection,
c_id: Uuid,
limit: usize,
offset: usize,
) -> Result<Vec<Replicant>, DbError> {
use crate::schema::replicants::dsl::*;
replicants
.filter(corp_id.eq(c_id))
.select(Replicant::as_select())
.limit(limit as i64)
.offset(offset as i64)
.load(conn)
.await
.map_err(Into::into)
}
pub async fn get_corp_replicants_full(
conn: &mut AsyncPgConnection,
c_id: Uuid,
limit: usize,
offset: usize,
) -> Result<Vec<ReplicantFull>, DbError> {
use crate::schema::{replicants, replicants_stats};
let results = replicants::table
.inner_join(replicants_stats::table)
.filter(replicants::corp_id.eq(c_id))
.select((Replicant::as_select(), ReplicantStats::as_select()))
.limit(limit as i64)
.offset(offset as i64)
.load::<(Replicant, ReplicantStats)>(conn)
.await?;
Ok(results
.into_iter()
.map(|(rep, stats)| ReplicantFull {
id: rep.id,
name: rep.name,
description: rep.description,
gender: rep.gender,
status: rep.status,
created_at: rep.created_at,
is_private: rep.is_private,
firmware_file: rep.firmware_file,
corp_id: rep.corp_id,
health: stats.health,
strength: stats.strength,
intelligence: stats.intelligence,
})
.collect())
}
pub async fn get_replicant_full(
conn: &mut AsyncPgConnection,
replicant_id: Uuid,
) -> Result<ReplicantFull, DbError> {
use crate::schema::{replicants, replicants_stats};
let (rep, stats) = replicants::table
.inner_join(replicants_stats::table)
.filter(replicants::id.eq(replicant_id))
.select((Replicant::as_select(), ReplicantStats::as_select()))
.first(conn)
.await
.map_err(|e| match e {
diesel::result::Error::NotFound => DbError::NotFound,
_ => DbError::from(e),
})?;
Ok(ReplicantFull {
id: rep.id,
name: rep.name,
description: rep.description,
gender: rep.gender,
status: rep.status,
created_at: rep.created_at,
is_private: rep.is_private,
firmware_file: rep.firmware_file,
corp_id: rep.corp_id,
health: stats.health,
strength: stats.strength,
intelligence: stats.intelligence,
})
}
pub async fn get_stats(
conn: &mut AsyncPgConnection,
r_id: Uuid,
) -> Result<ReplicantStats, DbError> {
use crate::schema::replicants_stats::dsl::*;
replicants_stats
.find(r_id)
.first(conn)
.await
.map_err(Into::into)
}
pub async fn update_stats(
conn: &mut AsyncPgConnection,
r_id: Uuid,
stats: ReplicantStats,
) -> Result<ReplicantStats, DbError> {
use crate::schema::replicants_stats::dsl::*;
diesel::update(replicants_stats.find(r_id))
.set((
health.eq(stats.health),
strength.eq(stats.strength),
intelligence.eq(stats.intelligence),
))
.get_result(conn)
.await
.map_err(Into::into)
}
pub async fn get_count_by_status(
conn: &mut AsyncPgConnection,
status_query: ReplicantStatus,
) -> Result<i64, DbError> {
use crate::schema::replicants::dsl::*;
replicants
.filter(status.eq(status_query))
.count()
.get_result(conn)
.await
.map_err(Into::into)
}
pub async fn change_privacy(
conn: &mut AsyncPgConnection,
replicant_id: Uuid,
privacy: bool,
) -> Result<(), DbError> {
use crate::schema::replicants::dsl::*;
diesel::update(replicants.find(replicant_id))
.set(is_private.eq(privacy))
.execute(conn)
.await?;
Ok(())
}
pub async fn change_owner(
conn: &mut AsyncPgConnection,
replicant_id: Uuid,
new_owner_id: Uuid,
) -> Result<(), DbError> {
use crate::schema::replicants::dsl::*;
diesel::update(replicants.find(replicant_id))
.set((corp_id.eq(new_owner_id), is_private.eq(true)))
.execute(conn)
.await?;
Ok(())
}
pub async fn update_firmware(
conn: &mut AsyncPgConnection,
replicant_id: Uuid,
filename: String,
) -> Result<Replicant, DbError> {
use crate::schema::replicants::dsl::*;
diesel::update(replicants.find(replicant_id))
.set(firmware_file.eq(filename))
.get_result(conn)
.await
.map_err(Into::into)
}
}

View File

@@ -0,0 +1,137 @@
use crate::errors::DbError;
use crate::models::{Corp, NewUser, User, UserRole};
use diesel::prelude::*;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use uuid::Uuid;
pub struct UserRepository;
impl UserRepository {
pub async fn create_user(
conn: &mut AsyncPgConnection,
new_user: NewUser,
) -> Result<User, DbError> {
use crate::schema::users;
diesel::insert_into(users::table)
.values(&new_user)
.get_result(conn)
.await
.map_err(Into::into)
}
pub async fn get_user(
conn: &mut AsyncPgConnection,
user_id: Uuid,
) -> Result<Option<User>, DbError> {
use crate::schema::users::dsl::*;
users
.find(user_id)
.first(conn)
.await
.optional()
.map_err(Into::into)
}
pub async fn find_by_username(
conn: &mut AsyncPgConnection,
username_query: &str,
) -> Result<Option<User>, DbError> {
use crate::schema::users::dsl::*;
users
.filter(username.eq(username_query))
.first(conn)
.await
.optional()
.map_err(Into::into)
}
pub async fn update_role(
conn: &mut AsyncPgConnection,
user_id: Uuid,
new_role: UserRole,
) -> Result<User, DbError> {
use crate::schema::users::dsl::*;
diesel::update(users.find(user_id))
.set(role.eq(new_role))
.get_result(conn)
.await
.map_err(Into::into)
}
pub async fn update_corp_id(
conn: &mut AsyncPgConnection,
user_id: Uuid,
c_id: Option<Uuid>,
) -> Result<User, DbError> {
use crate::schema::users::dsl::*;
diesel::update(users.find(user_id))
.set(corp_id.eq(c_id))
.get_result(conn)
.await
.map_err(Into::into)
}
pub async fn get_user_corp(
conn: &mut AsyncPgConnection,
user_id: Uuid,
) -> Result<Option<Corp>, DbError> {
use crate::schema::{corps, users};
corps::table
.inner_join(users::table.on(users::corp_id.eq(corps::id.nullable())))
.filter(users::id.eq(user_id))
.select(corps::all_columns)
.first(conn)
.await
.optional()
.map_err(Into::into)
}
pub async fn get_user_with_corp(
conn: &mut AsyncPgConnection,
user_id: Uuid,
) -> Result<Option<(User, Corp)>, DbError> {
use crate::schema::{corps, users};
users::table
.find(user_id)
.inner_join(corps::table)
.first(conn)
.await
.optional()
.map_err(Into::into)
}
pub async fn get_users_by_corp_id(
conn: &mut AsyncPgConnection,
c_id: Uuid,
) -> Result<Vec<User>, DbError> {
use crate::schema::users::dsl::*;
users
.filter(corp_id.eq(c_id))
.load(conn)
.await
.map_err(Into::into)
}
pub async fn get_corp_admin(
conn: &mut AsyncPgConnection,
c_id: Uuid,
) -> Result<Option<User>, DbError> {
use crate::schema::users::dsl::*;
users
.filter(corp_id.eq(c_id))
.filter(role.eq(UserRole::CorpAdmin))
.first(conn)
.await
.optional()
.map_err(Into::into)
}
}

View File

@@ -0,0 +1,79 @@
// @generated automatically by Diesel CLI.
pub mod sql_types {
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
#[diesel(postgres_type(name = "replicant_gender"))]
pub struct ReplicantGender;
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
#[diesel(postgres_type(name = "replicant_status"))]
pub struct ReplicantStatus;
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
#[diesel(postgres_type(name = "user_role"))]
pub struct UserRole;
}
diesel::table! {
corps (id) {
id -> Uuid,
#[max_length = 255]
name -> Varchar,
description -> Text,
created_at -> Timestamp,
#[max_length = 255]
invite_code -> Varchar,
}
}
diesel::table! {
use diesel::sql_types::*;
use super::sql_types::ReplicantStatus;
use super::sql_types::ReplicantGender;
replicants (id) {
id -> Uuid,
#[max_length = 255]
name -> Varchar,
description -> Text,
status -> ReplicantStatus,
created_at -> Timestamp,
gender -> ReplicantGender,
corp_id -> Uuid,
is_private -> Bool,
#[max_length = 255]
firmware_file -> Nullable<Varchar>,
}
}
diesel::table! {
replicants_stats (replicant_id) {
replicant_id -> Uuid,
health -> Int4,
strength -> Int4,
intelligence -> Int4,
created_at -> Timestamp,
}
}
diesel::table! {
use diesel::sql_types::*;
use super::sql_types::UserRole;
users (id) {
id -> Uuid,
#[max_length = 255]
username -> Varchar,
#[max_length = 255]
password -> Varchar,
role -> UserRole,
created_at -> Timestamp,
corp_id -> Nullable<Uuid>,
}
}
diesel::joinable!(replicants -> corps (corp_id));
diesel::joinable!(replicants_stats -> replicants (replicant_id));
diesel::joinable!(users -> corps (corp_id));
diesel::allow_tables_to_appear_in_same_query!(corps, replicants, replicants_stats, users,);