init
This commit is contained in:
25
ticktalk/crates/db/src/errors.rs
Executable file
25
ticktalk/crates/db/src/errors.rs
Executable 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
32
ticktalk/crates/db/src/lib.rs
Executable 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")
|
||||
}
|
||||
56
ticktalk/crates/db/src/models.rs
Executable file
56
ticktalk/crates/db/src/models.rs
Executable 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,
|
||||
}
|
||||
60
ticktalk/crates/db/src/repositories/chat.rs
Executable file
60
ticktalk/crates/db/src/repositories/chat.rs
Executable 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())
|
||||
}
|
||||
}
|
||||
47
ticktalk/crates/db/src/repositories/message.rs
Executable file
47
ticktalk/crates/db/src/repositories/message.rs
Executable 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
7
ticktalk/crates/db/src/repositories/mod.rs
Executable file
7
ticktalk/crates/db/src/repositories/mod.rs
Executable 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;
|
||||
85
ticktalk/crates/db/src/repositories/user.rs
Executable file
85
ticktalk/crates/db/src/repositories/user.rs
Executable 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
33
ticktalk/crates/db/src/schema.rs
Executable file
33
ticktalk/crates/db/src/schema.rs
Executable 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,);
|
||||
Reference in New Issue
Block a user