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,25 @@
use diesel_async::pooled_connection::bb8::RunError;
#[derive(Debug, thiserror::Error)]
pub enum RepositoryError {
#[error("Database error: {0}")]
Query(#[from] diesel::result::Error),
#[error("Connection error: {0}")]
Connection(#[from] diesel::ConnectionError),
#[error("Pool error: {0}")]
Pool(#[from] RunError),
#[error("Not found")]
NotFound,
#[error("Already exists")]
AlreadyExists,
#[error("Validation error: {0}")]
Validation(String),
#[error("Database conflict")]
Conflict,
}

32
ticktalk/crates/db/src/lib.rs Executable file
View File

@@ -0,0 +1,32 @@
pub mod errors;
pub mod models;
pub mod repositories;
mod schema;
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
use diesel_migrations::{EmbeddedMigrations, embed_migrations};
use std::env;
pub use diesel_async::AsyncPgConnection;
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
pub type DbPool = 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() -> DbPool {
let database_url = database_url();
let config = AsyncDieselConnectionManager::<AsyncPgConnection>::new(database_url);
DbPool::builder()
.build(config)
.await
.expect("Failed to create pool")
}

View File

@@ -0,0 +1,56 @@
use chrono::NaiveDateTime;
use diesel::prelude::*;
use uuid::Uuid;
#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
#[diesel(table_name = crate::schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct User {
pub id: Uuid,
pub username: String,
pub created_at: NaiveDateTime,
}
#[derive(Debug, Clone, Insertable)]
#[diesel(table_name = crate::schema::users)]
pub struct NewUser {
pub username: String,
}
#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
#[diesel(table_name = crate::schema::chats)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Chat {
pub id: Uuid,
pub first_user_id: Uuid,
pub second_user_id: Uuid,
pub created_at: NaiveDateTime,
}
#[derive(Debug, Clone, Insertable)]
#[diesel(table_name = crate::schema::chats)]
pub struct NewChat {
pub first_user_id: Uuid,
pub second_user_id: Uuid,
}
#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
#[diesel(table_name = crate::schema::messages)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Message {
pub id: Uuid,
pub sender_id: Uuid,
pub recipient_id: Uuid,
pub content: String,
pub created_at: NaiveDateTime,
pub chat_id: Uuid,
}
#[derive(Debug, Clone, Insertable)]
#[diesel(table_name = crate::schema::messages)]
pub struct NewMessage {
pub sender_id: Uuid,
pub recipient_id: Uuid,
pub content: String,
pub chat_id: Uuid,
}

View File

@@ -0,0 +1,60 @@
use crate::errors::RepositoryError;
use crate::models::Chat;
use crate::{DbPool, models::NewChat};
use diesel::{BoolExpressionMethods, ExpressionMethods, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use uuid::Uuid;
#[derive(Clone)]
pub struct ChatRepository {
pool: DbPool,
}
impl ChatRepository {
pub fn new(pool: DbPool) -> Self {
Self { pool }
}
pub async fn find_by_id(&self, chat_id: Uuid) -> Result<Option<Chat>, RepositoryError> {
use crate::schema::chats::dsl::*;
let mut conn = self.pool.get().await?;
match chats
.find(chat_id)
.select(Chat::as_select())
.first(&mut conn)
.await
{
Ok(chat) => Ok(Some(chat)),
Err(diesel::result::Error::NotFound) => Ok(None),
Err(e) => Err(RepositoryError::Query(e)),
}
}
pub async fn create(&self, new_chat: NewChat) -> Result<Chat, RepositoryError> {
use crate::schema::chats::dsl::*;
let mut conn = self.pool.get().await?;
diesel::insert_into(chats)
.values(&new_chat)
.returning(Chat::as_returning())
.get_result(&mut conn)
.await
.map_err(|e| e.into())
}
pub async fn find_by_user(&self, user: Uuid) -> Result<Vec<Chat>, RepositoryError> {
use crate::schema::chats::dsl::*;
let mut conn = self.pool.get().await?;
chats
.filter(first_user_id.eq(user).or(second_user_id.eq(user)))
.select(Chat::as_select())
.load(&mut conn)
.await
.map_err(|e| e.into())
}
}

View File

@@ -0,0 +1,47 @@
use crate::{
DbPool,
errors::RepositoryError,
models::{Message, NewMessage},
};
use diesel::SelectableHelper;
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use uuid::Uuid;
#[derive(Clone)]
pub struct MessageRepository {
pool: DbPool,
}
impl MessageRepository {
pub fn new(pool: DbPool) -> Self {
Self { pool }
}
pub async fn create(&self, new_message: NewMessage) -> Result<Message, RepositoryError> {
use crate::schema::messages::dsl::*;
let mut conn = self.pool.get().await?;
match diesel::insert_into(messages)
.values(&new_message)
.returning(Message::as_returning())
.get_result(&mut conn)
.await
{
Ok(message) => Ok(message),
Err(e) => Err(RepositoryError::from(e)),
}
}
pub async fn find_by_chat_id(&self, c_id: Uuid) -> Result<Vec<Message>, RepositoryError> {
use crate::schema::messages::dsl::*;
let mut conn = self.pool.get().await?;
match messages.filter(chat_id.eq(c_id)).load(&mut conn).await {
Ok(chat_messages) => Ok(chat_messages),
Err(e) => Err(RepositoryError::from(e)),
}
}
}

View File

@@ -0,0 +1,7 @@
mod chat;
mod message;
mod user;
pub use self::chat::ChatRepository;
pub use self::message::MessageRepository;
pub use self::user::UserRepository;

View File

@@ -0,0 +1,85 @@
use crate::errors::RepositoryError;
use crate::models::{NewUser, User};
use crate::schema::users;
use diesel::result::Error as DieselError;
use diesel::{ExpressionMethods, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use uuid::Uuid;
use crate::DbPool;
#[derive(Clone)]
pub struct UserRepository {
pool: DbPool,
}
impl UserRepository {
pub fn new(pool: DbPool) -> Self {
Self { pool }
}
pub async fn find_by_id(&self, user_id: Uuid) -> Result<Option<User>, RepositoryError> {
use crate::schema::users::dsl::*;
let mut conn = self.pool.get().await?;
match users
.find(user_id)
.select(User::as_select())
.first(&mut conn)
.await
{
Ok(user) => Ok(Some(user)),
Err(diesel::result::Error::NotFound) => Ok(None),
Err(e) => Err(RepositoryError::Query(e)),
}
}
pub async fn find_by_username(&self, user_name: String) -> Result<Option<User>, RepositoryError> {
use crate::schema::users::dsl::*;
let mut conn = self.pool.get().await?;
match users
.filter(username.eq(user_name))
.select(User::as_select())
.first(&mut conn)
.await
{
Ok(user) => Ok(Some(user)),
Err(diesel::result::Error::NotFound) => Ok(None),
Err(e) => Err(RepositoryError::Query(e)),
}
}
pub async fn create(&self, user: NewUser) -> Result<User, RepositoryError> {
use crate::schema::users::dsl::*;
let mut conn = self.pool.get().await?;
match diesel::insert_into(users)
.values(&user)
.returning(User::as_returning())
.get_result(&mut conn)
.await
{
Ok(created) => Ok(created),
Err(DieselError::DatabaseError(
diesel::result::DatabaseErrorKind::UniqueViolation,
ref info,
)) => {
// Проверяем, какое именно ограничение нарушено
let constraint_name = info.constraint_name();
let error_message = if constraint_name == Some("users_username_key") {
"Пользователь с таким username уже существует".to_string()
} else if constraint_name == Some("users_email_key") {
"Пользователь с таким email уже существует".to_string()
} else {
"Нарушено ограничение уникальности".to_string()
};
Err(RepositoryError::Conflict)
}
Err(e) => Err(e.into()),
}
}
}

View File

@@ -0,0 +1,33 @@
// @generated automatically by Diesel CLI.
diesel::table! {
chats (id) {
id -> Uuid,
first_user_id -> Uuid,
second_user_id -> Uuid,
created_at -> Timestamp,
}
}
diesel::table! {
messages (id) {
id -> Uuid,
sender_id -> Uuid,
recipient_id -> Uuid,
content -> Text,
created_at -> Timestamp,
chat_id -> Uuid,
}
}
diesel::table! {
users (id) {
id -> Uuid,
username -> Text,
created_at -> Timestamp,
}
}
diesel::joinable!(messages -> chats (chat_id));
diesel::allow_tables_to_appear_in_same_query!(chats, messages, users,);