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,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,);