Add User Handling to CoveDb

and..
- Add Delete SqlPart
- Cleanup imports
This commit is contained in:
CanadianBaconBoi 2026-02-23 16:22:26 +01:00
parent 5fedceb383
commit c62bb41cd4
25 changed files with 312 additions and 153 deletions

2
Cargo.lock generated
View File

@ -75,7 +75,6 @@ dependencies = [
"cove-db",
"cove-net-common",
"cove-net-server",
"scc",
"sqlx",
"tokio",
]
@ -191,6 +190,7 @@ name = "cove-db"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"cove-db-macros",
"cove-net-common",
"serde_json",

View File

@ -10,4 +10,3 @@ tokio.workspace = true
cove-db.workspace = true
sqlx.workspace = true
anyhow.workspace = true
scc.workspace = true

View File

@ -2,14 +2,11 @@ use cove_net_server::message::middleware::auth::AuthTokenMiddleware;
use std::net::{IpAddr, Ipv4Addr};
use std::sync::{Arc};
use std::time::Duration;
use sqlx::{Execute, Executor};
use sqlx::postgres::PgQueryResult;
use sqlx::types::time::OffsetDateTime;
use cove_db::{CoveDB, CoveDBImpl};
use cove_db::part::{BindQueryBuilder, SqlPart};
use cove_db::part::condition::ConditionType;
use cove_db::rows::{InsertableRow, SelectableRow, TableRow, WhereRow};
use cove_db::rows::user::{PartialUserRow, UserRow};
use cove_db::CoveDB;
use cove_db::query::user::UserQueries;
use cove_db::rows::PartialTableRow;
use cove_db::rows::user::UserRow;
use cove_db::types::user_status::UserStatus;
use cove_net_common::id::message_type::MessageType;
use cove_net_common::id::SnowflakeID;
@ -27,55 +24,59 @@ async fn main() -> Result<(), anyhow::Error> {
db.run_migrations().await?;
db.run_system_migrations().await?;
// let user_row = UserRow {
// id: SnowflakeID::new_random_hex_loc(MessageType::User, "beefcafe")?,
// username: "CanadianBacon".to_string(),
// discriminator: "0001".to_string(),
// avatar_hash: None,
// email: "bc.bacon.bits@gmail.com".to_string(),
// email_verified: true,
// password_hash: "1802vgu12890n7b489127".to_string(),
// mfa_enabled: false,
// mfa_secret: None,
// status: UserStatus::Online,
// public_flags: 0,
// locale: "en-US".to_string(),
// premium_since: Some(OffsetDateTime::now_utc() + Duration::from_hours(1)),
// premium_end: Some(OffsetDateTime::now_utc() + Duration::from_hours(24)),
// bot: false,
// bot_oauth_scopes: Default::default(),
// preferences: Default::default(),
// created_at: OffsetDateTime::now_utc(),
// updated_at: OffsetDateTime::now_utc(),
// };
//
// let mut query_builder = BindQueryBuilder::new();
// user_row.insert().encode(&mut query_builder)?;
//
// let query = query_builder.to_query();
// let query = user_row.bind(query)?;
//
// let res = db.run_query::<PgQueryResult>(query.sql_query).await?;
// println!("{} rows affected", res.rows_affected());
let request_row: <UserRow as TableRow>::PartialRow = PartialUserRow {
username: Some("CanadianBacon".to_string()),
..Default::default()
let user_row = UserRow {
id: SnowflakeID::new_random_hex_loc(MessageType::User, "beefcafe")?,
username: "CanadianBacon".to_string(),
discriminator: "0001".to_string(),
avatar_hash: None,
email: "bc.bacon.bits@gmail.com".to_string(),
email_verified: true,
password_hash: "1802vgu12890n7b489127".to_string(),
mfa_enabled: false,
mfa_secret: None,
status: UserStatus::Online,
public_flags: 0,
locale: "en-US".to_string(),
premium_since: Some(OffsetDateTime::now_utc() + Duration::from_hours(1)),
premium_end: Some(OffsetDateTime::now_utc() + Duration::from_hours(24)),
bot: false,
bot_oauth_scopes: Default::default(),
preferences: Default::default(),
created_at: OffsetDateTime::now_utc(),
updated_at: OffsetDateTime::now_utc(),
};
let mut query_builder = BindQueryBuilder::new();
request_row.select(vec!["id", "username"]).encode(&mut query_builder)?;
let sql_where = request_row.wheres(|w|{
w.cond_and::<String>("username", ConditionType::Equal(false))
})?;
sql_where.encode(&mut query_builder)?;
let query = request_row.bind(sql_where, query_builder.to_query())?;
println!("{}", query.sql_query.sql());
let out_partial_row: <UserRow as TableRow>::PartialRow = db.get_pool().fetch_one(query.sql_query).await?.into();
println!("{:?}, {:?}, {:?}", out_partial_row.id, out_partial_row.username, out_partial_row.email);
match db.create_user(user_row).await {
Ok(result) => {
println!("Create User: {} rows affected", result.result().as_ref().unwrap().rows_affected());
match db.get_user_by_name_and_discrim("CanadianBacon", "0001").await {
Ok(partial_user) => {
match partial_user.value().get_full(&db).await {
Ok(full_user) => {
match db.delete_user_by_id(full_user.id).await {
Ok(result) => {
println!("Delete User: {} rows affected", result.result().as_ref().unwrap().rows_affected());
println!("{:?}", result.value());
}
Err(err) => {
println!("Failed to delete user: {}", err);
}
}
}
Err(err) => {
println!("Failed to get full user: {}", err);
}
}
}
Err(err) => {
println!("Failed to get user: {}", err);
}
}
}
Err(err) => {
println!("Failed to create user: {:?}", err);
}
}
http_testing(db).await?;

View File

@ -8,5 +8,6 @@ sqlx.workspace = true
anyhow.workspace = true
serde_json.workspace = true
cove-db-macros.workspace = true
async-trait.workspace = true
cove-net-common.workspace = true

View File

@ -148,6 +148,10 @@ fn impl_derive_table_row(ast: DeriveInput) -> TokenStream {
impl crate::rows::SelectableRow for #partial_name {}
};
let impl_deletable_row = quote! {
impl crate::rows::DeletableRow for #partial_name {}
};
let impl_where_row = {
let match_body = {
let mut ret = quote!();
@ -163,7 +167,26 @@ fn impl_derive_table_row(ast: DeriveInput) -> TokenStream {
ret
};
let where_all_body = {
let mut ret = quote!();
for field in fields.named.iter() {
let name = field.ident.as_ref().unwrap();
let quoted_name = format!("{}", name);
let ty = &field.ty;
ret.extend(
quote! {
if self.#name.is_some() {
wheres = wheres.cond_and::<#ty>(#quoted_name, ConditionType::Equal(false)).unwrap();
}
}
)
}
ret
};
quote! {
use crate::part::condition::ConditionType;
use crate::part::sql_where::SqlWhere;
impl crate::rows::WhereRow for #partial_name {
fn bind<'a>(&'a self, wheres: crate::part::sql_where::SqlWhere, query: crate::part::BindQuery<'a>) -> Result<crate::part::BindQuery<'a>, Self::Error> {
let mut query = query;
@ -175,9 +198,13 @@ fn impl_derive_table_row(ast: DeriveInput) -> TokenStream {
}
Ok(query)
}
fn where_all(&self) -> SqlWhere {
let mut wheres = SqlWhere::new();
#where_all_body
wheres
}
}
}
};
@ -247,7 +274,9 @@ fn impl_derive_table_row(ast: DeriveInput) -> TokenStream {
ret.extend(struct_partial_row);
ret.extend(impl_table_row);
ret.extend(impl_partial_table_row);
// ret.extend(impl_deletable_row);
ret.extend(impl_insertable_row);
ret.extend(impl_deletable_row);
ret.extend(impl_selectable_row);
ret.extend(impl_where_row);
ret.extend(impl_try_from_pgrow_for_full);

View File

@ -4,6 +4,7 @@ pub mod part;
pub mod types;
use anyhow::Context;
use async_trait::async_trait;
use sqlx::{PgConnection, PgPool, Postgres};
use sqlx::migrate::Migrator;
use sqlx::postgres::{PgArguments, PgConnectOptions, PgPoolOptions, PgQueryResult};
@ -65,6 +66,7 @@ impl CoveDBImpl for CoveDB {
}
}
#[async_trait]
pub trait CoveDBImpl {
fn get_pool(&self) -> &PgPool;

View File

@ -0,0 +1,21 @@
use crate::part::{BindQueryBuilder, SqlPart};
pub struct SqlDelete {
table_name: String
}
impl SqlDelete {
pub fn with_table(table_name: impl Into<String>) -> SqlDelete {
SqlDelete {table_name: table_name.into()}
}
}
impl SqlPart for SqlDelete {
fn encode(&self, query_builder: &mut BindQueryBuilder) -> Result<(), anyhow::Error> {
query_builder.push("DELETE FROM ");
query_builder.push(&self.table_name);
query_builder.push(" ");
Ok(())
}
}

View File

@ -10,6 +10,7 @@ pub mod condition;
pub mod select;
pub mod sql_where;
pub mod insert;
pub mod delete;
pub trait SqlPart {
fn encode(&self, arg_buffer: &mut BindQueryBuilder) -> Result<(), anyhow::Error>;

View File

@ -4,6 +4,9 @@ use sqlx::{Encode, Postgres, Type};
use crate::part::condition::{ConditionJoin, ConditionType, SqlCondition};
use crate::part::{BindQueryBuilder, SqlPart};
unsafe impl Send for SqlWhere {}
unsafe impl Sync for SqlWhere {}
pub struct SqlWhere {
selected_scope: Arc<Mutex<SqlWhereScope>>,
pub indexed_placeholders: Vec<(usize, String)>,

View File

@ -1 +1,48 @@
pub mod text;
use sqlx::postgres::PgQueryResult;
use crate::rows::{PartialTableRow, TableRow};
pub mod user;
pub struct PartialQueryResult<T: PartialTableRow> {
value: T,
pg_query_result: Option<PgQueryResult>
}
impl<T: PartialTableRow> PartialQueryResult<T> {
pub fn new(value: T, pg_query_result: Option<PgQueryResult>) -> Self {
PartialQueryResult {
value,
pg_query_result
}
}
pub fn value(&self) -> &T {
&self.value
}
pub fn result(&self) -> &Option<PgQueryResult> {
&self.pg_query_result
}
}
pub struct QueryResult<T: TableRow> {
value: T,
pg_query_result: Option<PgQueryResult>
}
impl<T: TableRow> QueryResult<T> {
pub fn new(value: T, pg_query_result: Option<PgQueryResult>) -> Self {
QueryResult {
value,
pg_query_result
}
}
pub fn value(&self) -> &T {
&self.value
}
pub fn result(&self) -> &Option<PgQueryResult> {
&self.pg_query_result
}
}

View File

@ -1,59 +0,0 @@
use anyhow::{Error};
use sqlx::{Execute};
use sqlx::postgres::{ PgQueryResult};
use cove_net_common::id::message_type::MessageType;
use cove_net_common::id::SnowflakeID;
use cove_net_common::id::types::text::TextMessageType;
use cove_net_common::message::c2s::text::text::{TextEmbed, TextMessage};
use crate::{CoveDB, CoveDBImpl};
use crate::part::{BindQueryBuilder, SqlPart};
use crate::part::insert::SqlInsert;
pub trait TextQueries: CoveDBImpl + Send + Sync {
async fn create_message(
&self, user_id: SnowflakeID,
message: TextMessage, mentions: Option<Vec<SnowflakeID>>, mention_everyone: bool,
message_type: &TextMessageType
) -> Result<PgQueryResult, Error> {
println!("creating message");
let message_id = SnowflakeID::new_random_hex_loc(
MessageType::Text(TextMessageType::Text), "beefcafe"
)?;
let mut query_builder = BindQueryBuilder::new();
SqlInsert::with_table("messages")
.col::<SnowflakeID>("id")
.col::<SnowflakeID>("channel_id")
.col::<SnowflakeID>("guild_id")
.col::<SnowflakeID>("author_id")
.col::<String>("content")
.col::<bool>("tts")
.col::<bool>("mention_everyone")
.col::<TextMessageType>("message_type")
.opt_arr_col::<SnowflakeID>("mentions")
.opt_arr_col::<TextEmbed>("embeds")
.opt_arr_col::<SnowflakeID>("attachments")
.encode(&mut query_builder)?;
let query = query_builder.to_query().build()
.bind(message_id)
.bind(message.channel_id)
.bind(message.guild_id)
.bind(user_id)
.bind(message.content)
.bind(message.tts)
.bind(mention_everyone)
.bind(message_type)
.bind(mentions)
.bind(message.embeds)
.bind(message.attachments);
println!("{:?}", query.sql());
let res: PgQueryResult = self.run_query(query).await?;
println!("Rows Affected: {}", res.rows_affected());
Ok(res)
}
}
impl TextQueries for CoveDB {}

91
cove-db/src/query/user.rs Normal file
View File

@ -0,0 +1,91 @@
use async_trait::async_trait;
use sqlx::Executor;
use sqlx::postgres::PgQueryResult;
use cove_net_common::id::SnowflakeID;
use crate::{CoveDB, CoveDBImpl};
use crate::part::{BindQueryBuilder, SqlPart};
use crate::query::{PartialQueryResult, QueryResult};
use crate::rows::{DeletableRow, InsertableRow, SelectableRow, TableRow, WhereRow};
use crate::rows::user::{PartialUserRow, UserRow};
#[async_trait]
pub trait UserQueries: CoveDBImpl {
async fn create_user(&self, user_row: UserRow) -> Result<QueryResult<UserRow>, anyhow::Error> {
let mut query_builder = BindQueryBuilder::new();
user_row.insert().encode(&mut query_builder)?;
let query = query_builder.to_query();
let query = user_row.bind(query)?;
let res = self.run_query::<PgQueryResult>(query.sql_query).await?;
Ok(QueryResult::new(user_row, Some(res)))
}
async fn get_user_by_name_and_discrim(&self, name: impl Into<String> + Send, discriminator: impl Into<String> + Send) -> Result<PartialQueryResult<PartialUserRow>, anyhow::Error> {
let user_request_row = PartialUserRow {
username: Some(name.into()),
discriminator: Some(discriminator.into()),
..Default::default()
};
let mut query_builder = BindQueryBuilder::new();
user_request_row.select(vec!["id", "username", "discriminator"]).encode(&mut query_builder)?;
let sql_where = user_request_row.where_all();
sql_where.encode(&mut query_builder)?;
let query = user_request_row.bind(sql_where, query_builder.to_query())?;
let out_partial_row: <UserRow as TableRow>::PartialRow = self.get_pool().fetch_one(query.sql_query).await?.into();
Ok(PartialQueryResult::new(out_partial_row, None))
}
async fn get_user_by_id(&self, user_id: SnowflakeID) -> Result<PartialQueryResult<PartialUserRow>, anyhow::Error> {
let user_request_row = PartialUserRow {
id: Some(user_id),
..Default::default()
};
let mut query_builder = BindQueryBuilder::new();
user_request_row.select(vec!["id", "username", "discriminator"]).encode(&mut query_builder)?;
let sql_where = user_request_row.where_all();
sql_where.encode(&mut query_builder)?;
let query = user_request_row.bind(sql_where, query_builder.to_query())?;
let out_partial_row: <UserRow as TableRow>::PartialRow = self.get_pool().fetch_one(query.sql_query).await?.into();
Ok(PartialQueryResult::new(out_partial_row, None))
}
async fn delete_user_by_id(&self, user_id: SnowflakeID) -> Result<QueryResult<UserRow>, anyhow::Error> {
let user_delete_row = PartialUserRow {
id: Some(user_id),
..Default::default()
};
let mut query_builder = BindQueryBuilder::new();
user_delete_row.select(vec!["*"]).encode(&mut query_builder)?;
let sql_where = user_delete_row.where_all();
sql_where.encode(&mut query_builder)?;
let query = user_delete_row.bind(sql_where, query_builder.to_query())?;
let out_val: UserRow = self.get_pool().fetch_one(query.sql_query).await?.try_into()?;
let mut query_builder = BindQueryBuilder::new();
user_delete_row.delete().encode(&mut query_builder)?;
let sql_where = user_delete_row.where_all();
sql_where.encode(&mut query_builder)?;
let query = user_delete_row.bind(sql_where, query_builder.to_query())?;
let res = self.run_query::<PgQueryResult>(query.sql_query).await?;
Ok(QueryResult::new(out_val, Some(res)))
}
}
#[async_trait]
impl UserQueries for CoveDB {}

View File

@ -1,4 +1,9 @@
use crate::part::BindQuery;
use async_trait::async_trait;
use sqlx::Executor;
use sqlx::postgres::PgRow;
use crate::{CoveDB, CoveDBImpl};
use crate::part::{BindQuery, BindQueryBuilder, SqlPart};
use crate::part::delete::SqlDelete;
use crate::part::insert::SqlInsert;
use crate::part::select::SqlSelect;
use crate::part::sql_where::SqlWhere;
@ -17,12 +22,33 @@ pub trait TableRow {
fn get_table_name() -> &'static str;
}
#[async_trait]
pub trait PartialTableRow {
type Error;
type FullTableRow: TableRow;
fn get_table_name() -> &'static str {
Self::FullTableRow::get_table_name()
}
async fn get_full(&self, db: &CoveDB) -> Result<Self::FullTableRow, Self::Error> where
Self: SelectableRow + WhereRow,
<Self as PartialTableRow>::Error: From<anyhow::Error>,
Self::FullTableRow: TryFrom<PgRow>,
<<Self as PartialTableRow>::FullTableRow as TryFrom<PgRow>>::Error: Into<anyhow::Error>
{
let mut query_builder = BindQueryBuilder::new();
self.select(vec!["*"]).encode(&mut query_builder)?;
let sql_where = self.where_all();
sql_where.encode(&mut query_builder)?;
let query = self.bind(sql_where, query_builder.to_query())?;
let out_full_row: Self::FullTableRow = db.get_pool()
.fetch_one(query.sql_query).await.map_err(|e|e.into())?
.try_into().map_err(|e: <Self::FullTableRow as TryFrom<PgRow>>::Error | e.into())?;
Ok(out_full_row)
}
}
pub trait InsertableRow: TableRow {
@ -40,12 +66,20 @@ pub trait SelectableRow: PartialTableRow {
}
}
pub trait DeletableRow: PartialTableRow {
fn delete(&'_ self) -> SqlDelete {
SqlDelete::with_table(Self::get_table_name())
}
}
pub trait WhereRow: PartialTableRow {
fn wheres(&'_ self, where_fn: impl Fn(SqlWhere) -> Result<SqlWhere, Self::Error>) -> Result<SqlWhere, Self::Error> {
fn wheres(&'_ self, where_fn: impl Fn(SqlWhere) -> Result<SqlWhere, Self::Error> + Send + Sync) -> Result<SqlWhere, Self::Error> {
let wheres = SqlWhere::new();
let wheres = where_fn(wheres)?;
Ok(wheres)
}
fn where_all(&'_ self) -> SqlWhere;
fn bind<'a>(&'a self, wheres: SqlWhere, query: BindQuery<'a>) -> Result<BindQuery<'a>, Self::Error>;
}

View File

@ -4,7 +4,7 @@ use cove_db_macros::DeriveTableRow;
use cove_net_common::id::SnowflakeID;
use crate::types::user_status::UserStatus;
#[derive(DeriveTableRow)]
#[derive(DeriveTableRow, Debug)]
#[table_name("users")]
pub struct UserRow {
pub id: SnowflakeID,

View File

@ -3,7 +3,7 @@ use sqlx::encode::IsNull;
use sqlx::error::BoxDynError;
use sqlx::postgres::PgTypeInfo;
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum UserStatus {
Online,
Idle,

View File

@ -1,7 +1,7 @@
pub mod message;
use cove_db::CoveDB;
use std::any::{Any, TypeId};
use std::any::Any;
use std::collections::VecDeque;
use std::net::{IpAddr, SocketAddr};
use http_body_util::{BodyExt, Full};

View File

@ -1,11 +1,9 @@
use hyper::body::Buf;
use http_body_util::BodyExt;
use std::collections::VecDeque;
use anyhow::Error;
use async_trait::async_trait;
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response};
use hyper::body::Bytes;
use hyper::Response;
use hyper::http::request::Parts;
use cove_net_common::message::c2s::account::login::LoginMessage;
use cove_net_common::message::c2s::ClientToServerMessage;

View File

@ -1,11 +1,9 @@
use hyper::body::Buf;
use http_body_util::BodyExt;
use std::collections::VecDeque;
use anyhow::Error;
use async_trait::async_trait;
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response};
use hyper::body::Bytes;
use hyper::Response;
use hyper::http::request::Parts;
use cove_net_common::message::c2s::account::register::RegisterMessage;
use cove_net_common::message::c2s::ClientToServerMessage;

View File

@ -2,8 +2,8 @@ use std::collections::VecDeque;
use anyhow::{Error};
use async_trait::async_trait;
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response};
use hyper::body::Bytes;
use hyper::Response;
use hyper::http::request::Parts;
use cove_net_common::message::c2s::ClientToServerMessage;
use crate::message::handlers::base::middleware::{AssociatedDataMap};

View File

@ -2,8 +2,8 @@ use std::any::{Any, TypeId};
use anyhow::Error;
use async_trait::async_trait;
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response};
use hyper::body::Bytes;
use hyper::Response;
use hyper::http::request::Parts;
use crate::message::StdHashMap;

View File

@ -1,10 +1,10 @@
use std::any::{Any, TypeId};
use std::any::TypeId;
use std::collections::VecDeque;
use std::sync::Arc;
use anyhow::{anyhow, Error};
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response};
use hyper::body::Bytes;
use hyper::Response;
use hyper::http::request::Parts;
use scc::HashMap;
use crate::message::handlers::base::handler::CoveRequestHandler;

View File

@ -1,11 +1,9 @@
use hyper::body::Buf;
use http_body_util::BodyExt;
use std::collections::VecDeque;
use anyhow::Error;
use async_trait::async_trait;
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response};
use hyper::body::Bytes;
use hyper::Response;
use hyper::http::request::Parts;
use cove_net_common::message::c2s::ClientToServerMessage;
use cove_net_common::message::c2s::text::attachment::AttachmentMessage;

View File

@ -1,11 +1,9 @@
use hyper::body::Buf;
use http_body_util::BodyExt;
use std::collections::VecDeque;
use anyhow::Error;
use async_trait::async_trait;
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response};
use hyper::body::Bytes;
use hyper::Response;
use hyper::http::request::Parts;
use cove_net_common::message::c2s::ClientToServerMessage;
use cove_net_common::message::c2s::text::reaction::ReactionMessage;

View File

@ -1,11 +1,9 @@
use hyper::body::Buf;
use http_body_util::BodyExt;
use std::collections::VecDeque;
use anyhow::Error;
use async_trait::async_trait;
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response};
use hyper::body::Bytes;
use hyper::Response;
use hyper::http::request::Parts;
use cove_net_common::message::c2s::ClientToServerMessage;
use cove_net_common::message::c2s::text::text::TextMessage;

View File

@ -1,10 +1,9 @@
use std::any::TypeId;
use std::time::SystemTime;
use anyhow::Error;
use async_trait::async_trait;
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response, StatusCode};
use hyper::body::Bytes;
use hyper::{Response, StatusCode};
use hyper::http::request::Parts;
use cove_net_common::id::message_type::MessageType;
use cove_net_common::id::SnowflakeID;