init
This commit is contained in:
1
dollhouse/crates/dollhouse-db/.env
Executable file
1
dollhouse/crates/dollhouse-db/.env
Executable file
@@ -0,0 +1 @@
|
||||
DATABASE_URL=postgres://dollhouse_user:hahahadollhouse@localhost:5432/dollhouse_db
|
||||
16
dollhouse/crates/dollhouse-db/Cargo.toml
Executable file
16
dollhouse/crates/dollhouse-db/Cargo.toml
Executable 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"
|
||||
9
dollhouse/crates/dollhouse-db/diesel.toml
Executable file
9
dollhouse/crates/dollhouse-db/diesel.toml
Executable 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"
|
||||
0
dollhouse/crates/dollhouse-db/migrations/.diesel_lock
Executable file
0
dollhouse/crates/dollhouse-db/migrations/.diesel_lock
Executable file
0
dollhouse/crates/dollhouse-db/migrations/.keep
Executable file
0
dollhouse/crates/dollhouse-db/migrations/.keep
Executable file
@@ -0,0 +1,6 @@
|
||||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
||||
@@ -0,0 +1,36 @@
|
||||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
|
||||
|
||||
|
||||
-- Sets up a trigger for the given table to automatically set a column called
|
||||
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||
-- in the modified columns)
|
||||
--
|
||||
-- # Example
|
||||
--
|
||||
-- ```sql
|
||||
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||
--
|
||||
-- SELECT diesel_manage_updated_at('users');
|
||||
-- ```
|
||||
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF (
|
||||
NEW IS DISTINCT FROM OLD AND
|
||||
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||
) THEN
|
||||
NEW.updated_at := current_timestamp;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -0,0 +1,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;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
35
dollhouse/crates/dollhouse-db/src/errors.rs
Executable file
35
dollhouse/crates/dollhouse-db/src/errors.rs
Executable 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
33
dollhouse/crates/dollhouse-db/src/lib.rs
Executable file
33
dollhouse/crates/dollhouse-db/src/lib.rs
Executable 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")
|
||||
}
|
||||
175
dollhouse/crates/dollhouse-db/src/models.rs
Executable file
175
dollhouse/crates/dollhouse-db/src/models.rs
Executable 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
173
dollhouse/crates/dollhouse-db/src/repositories/corp.rs
Executable file
173
dollhouse/crates/dollhouse-db/src/repositories/corp.rs
Executable 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)
|
||||
}
|
||||
}
|
||||
7
dollhouse/crates/dollhouse-db/src/repositories/mod.rs
Executable file
7
dollhouse/crates/dollhouse-db/src/repositories/mod.rs
Executable 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;
|
||||
334
dollhouse/crates/dollhouse-db/src/repositories/replicant.rs
Executable file
334
dollhouse/crates/dollhouse-db/src/repositories/replicant.rs
Executable 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)
|
||||
}
|
||||
}
|
||||
137
dollhouse/crates/dollhouse-db/src/repositories/user.rs
Executable file
137
dollhouse/crates/dollhouse-db/src/repositories/user.rs
Executable 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)
|
||||
}
|
||||
}
|
||||
79
dollhouse/crates/dollhouse-db/src/schema.rs
Executable file
79
dollhouse/crates/dollhouse-db/src/schema.rs
Executable 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,);
|
||||
Reference in New Issue
Block a user