use sqlx's query macro in some places
Some checks failed
Docker Image CI / build_amd64 (push) Has been cancelled
Docker Image CI / build_arm64 (push) Has been cancelled
Docker Image CI / create_manifest (push) Has been cancelled

This commit is contained in:
2025-06-27 13:30:38 -04:00
parent c054ef1c1c
commit a1cd0e6a25
8 changed files with 82 additions and 95 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/target /target
.env .env
.vscode

View File

@@ -6,9 +6,9 @@ use poise::serenity_prelude as serenity;
#[poise::command(slash_command, prefix_command, aliases("bal", "b"))] #[poise::command(slash_command, prefix_command, aliases("bal", "b"))]
pub async fn balance(ctx: Context<'_>, user: Option<serenity::User>) -> Result<(), Error> { pub async fn balance(ctx: Context<'_>, user: Option<serenity::User>) -> Result<(), Error> {
let user = user.as_ref().unwrap_or(ctx.author()); let user = user.as_ref().unwrap_or(ctx.author());
let db = &ctx.data().database; let mut tx = ctx.data().database.begin().await?;
let wealth = super::get_balance(user.id, db).await?; let wealth = super::get_balance(user.id, &mut tx).await?;
common::no_ping_reply(&ctx, format!("{} **{}** token(s).", common::no_ping_reply(&ctx, format!("{} **{}** token(s).",
if user.id == ctx.author().id { if user.id == ctx.author().id {

View File

@@ -1,58 +1,54 @@
use crate::{Context, Error}; use crate::{Context, Error};
use poise::serenity_prelude::{UserId, User}; use poise::serenity_prelude::{UserId, User};
use sqlx::{types::chrono::{DateTime, Utc, TimeZone}, PgExecutor, Row}; use sqlx::{types::chrono::{DateTime, TimeZone, Utc}, PgConnection};
use std::time::Duration; use std::time::Duration;
async fn get_streak<'a, E>(db: E, user: UserId) -> Result<Option<i32>, Error> pub async fn get_streak(conn: &mut PgConnection, user: UserId) -> Result<Option<i32>, Error> {
where let result = sqlx::query!(
E: PgExecutor<'a>, "SELECT streak FROM dailies WHERE userid = $1",
{ user.get() as i64
match sqlx::query( )
"SELECT streak FROM dailies WHERE userid = $1" .fetch_optional(conn)
).bind(user.get() as i64).fetch_one(db).await .await?;
{
Ok(row) => Ok(Some(row.get(0))), Ok(result.map(|r| r.streak).unwrap_or(None))
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(Box::new(e)),
}
} }
async fn set_streak<'a, E>(db: E, user: UserId, streak: i32) -> Result<(), Error> pub async fn set_streak(conn: &mut PgConnection, user: UserId, streak: i32) -> Result<(), Error> {
where sqlx::query!(
E: PgExecutor<'a>, "INSERT INTO dailies (userid, streak) VALUES ($1, $2)
{ ON CONFLICT (userid) DO UPDATE SET streak = EXCLUDED.streak",
sqlx::query("INSERT INTO dailies (userid, streak) VALUES ($1, $2) ON CONFLICT (userid) DO UPDATE SET streak = EXCLUDED.streak") user.get() as i64,
.bind(user.get() as i64) streak
.bind(streak) )
.execute(db).await?; .execute(conn)
.await?;
Ok(()) Ok(())
} }
async fn get_last<'a, E>(db: E, user: UserId) -> Result<Option<DateTime<Utc>>, Error> pub async fn get_last(conn: &mut PgConnection, user: UserId) -> Result<Option<DateTime<Utc>>, Error> {
where let result = sqlx::query!(
E: PgExecutor<'a>, "SELECT last FROM dailies WHERE userid = $1",
{ user.get() as i64
match sqlx::query( )
"SELECT last FROM dailies WHERE userid = $1" .fetch_optional(conn)
).bind(user.get() as i64).fetch_one(db).await .await?;
{
Ok(row) => Ok(Some(row.get(0))), Ok(result.map(|r| r.last).unwrap_or(None))
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(Box::new(e)),
}
} }
async fn set_last<'a, E>(db: E, user: UserId, last: DateTime<Utc>) -> Result<(), Error> pub async fn set_last(conn: &mut PgConnection, user: UserId, last: DateTime<Utc>) -> Result<(), Error> {
where sqlx::query!(
E: PgExecutor<'a>, "INSERT INTO dailies (userid, last) VALUES ($1, $2)
{ ON CONFLICT (userid) DO UPDATE SET last = EXCLUDED.last",
sqlx::query("INSERT INTO dailies (userid, last) VALUES ($1, $2) ON CONFLICT (userid) DO UPDATE SET last = EXCLUDED.last") user.get() as i64,
.bind(user.get() as i64) last
.bind(last) )
.execute(db).await?; .execute(conn)
.await?;
Ok(()) Ok(())
} }
@@ -60,13 +56,14 @@ where
/// Tells you what your current daily streak is /// Tells you what your current daily streak is
#[poise::command(slash_command, prefix_command)] #[poise::command(slash_command, prefix_command)]
pub async fn streak(ctx: Context<'_>, user: Option<User>) -> Result<(), Error> { pub async fn streak(ctx: Context<'_>, user: Option<User>) -> Result<(), Error> {
let db = &ctx.data().database; let mut tx = ctx.data().database.begin().await?;
let (user, who) = match user { let (user, who) = match user {
Some(user) => (user.id, format!("{} has", user.display_name())), Some(user) => (user.id, format!("{} has", user.display_name())),
None => (ctx.author().id, "You have".to_string()), None => (ctx.author().id, "You have".to_string()),
}; };
ctx.reply(format!("{who} a daily streak of **{}**", get_streak(db, user).await?.unwrap_or(0))).await?; ctx.reply(format!("{who} a daily streak of **{}**", get_streak(&mut tx, user).await?.unwrap_or(0))).await?;
Ok(()) Ok(())
} }

View File

@@ -1,7 +1,6 @@
use crate::common::{Context, Error}; use crate::common::{Context, Error};
use poise::serenity_prelude::UserId; use poise::serenity_prelude::UserId;
use sqlx::Row;
enum LeaderboardType { enum LeaderboardType {
Tokens(usize), Tokens(usize),
@@ -13,15 +12,16 @@ async fn display_leaderboard(ctx: Context<'_>, t: LeaderboardType) -> Result<(),
match t { match t {
LeaderboardType::Tokens(count) => { LeaderboardType::Tokens(count) => {
let rows = sqlx::query( let rows = sqlx::query!(
r#" r#"
SELECT id, balance FROM bank SELECT id, balance FROM bank
ORDER BY balance DESC ORDER BY balance DESC
LIMIT $1 LIMIT $1
"# "#,
).bind(count as i32).fetch_all(db).await?; count as i32
).fetch_all(db).await?;
let users: Vec<(_, i32)> = rows.iter().map(|row| (UserId::new(row.get::<i64, _>(0) as u64), row.get(1))).collect(); let users: Vec<(_, i32)> = rows.iter().map(|row| (UserId::new(row.id as u64), row.balance.unwrap_or(100))).collect();
let mut output = String::new(); let mut output = String::new();
for (id, balance) in users { for (id, balance) in users {
@@ -32,15 +32,16 @@ async fn display_leaderboard(ctx: Context<'_>, t: LeaderboardType) -> Result<(),
ctx.reply(format!("```\n{output}```")).await?; ctx.reply(format!("```\n{output}```")).await?;
} }
LeaderboardType::Dailies(count) => { LeaderboardType::Dailies(count) => {
let rows = sqlx::query( let rows = sqlx::query!(
r#" r#"
SELECT userid, streak FROM dailies SELECT userid, streak FROM dailies
ORDER BY streak DESC ORDER BY streak DESC
LIMIT $1 LIMIT $1
"# "#,
).bind(count as i32).fetch_all(db).await?; count as i32
).fetch_all(db).await?;
let users: Vec<(_, i32)> = rows.iter().map(|row| (UserId::new(row.get::<i64, _>(0) as u64), row.get(1))).collect(); let users: Vec<(_, i32)> = rows.iter().map(|row| (UserId::new(row.userid as u64), row.streak.unwrap_or(0))).collect();
let mut output = String::new(); let mut output = String::new();
for (id, streak) in users { for (id, streak) in users {

View File

@@ -9,7 +9,7 @@ pub mod blackjack;
use crate::{inventory::{self, Inventory}, common::{Context, Error}}; use crate::{inventory::{self, Inventory}, common::{Context, Error}};
use poise::serenity_prelude::{self as serenity, futures::StreamExt, UserId}; use poise::serenity_prelude::{self as serenity, futures::StreamExt, UserId};
use sqlx::{Row, PgExecutor}; use sqlx::PgConnection;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Clone)] #[derive(Clone)]
@@ -79,16 +79,15 @@ mod items {
} }
} }
pub async fn get_balance<'a, E>(id: UserId, db: E) -> Result<i32, Error> pub async fn get_balance(id: UserId, db: &mut PgConnection) -> Result<i32, Error>
where
E: PgExecutor<'a>,
{ {
let row = sqlx::query("SELECT balance FROM bank WHERE id = $1") let row = sqlx::query!(
.bind(id.get() as i64) "SELECT balance FROM bank WHERE id = $1",
.fetch_one(db).await.ok(); id.get() as i64
).fetch_one(db).await.ok();
let balance = if let Some(row) = row { let balance = if let Some(row) = row {
row.try_get("balance")? row.balance.unwrap_or(100)
} else { } else {
100 100
}; };
@@ -96,14 +95,13 @@ where
Ok(balance) Ok(balance)
} }
pub async fn change_balance<'a, E>(id: UserId, balance: i32, db: E) -> Result<(), Error> pub async fn change_balance(id: UserId, balance: i32, db: &mut PgConnection) -> Result<(), Error>
where
E: PgExecutor<'a>,
{ {
sqlx::query("INSERT INTO bank (id, balance) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET balance = EXCLUDED.balance") sqlx::query!(
.bind(id.get() as i64) r#"INSERT INTO bank (id, balance) VALUES ($1, $2)
.bind(balance) ON CONFLICT (id) DO UPDATE SET balance = EXCLUDED.balance"#,
.execute(db).await?; id.get() as i64, balance
).execute(db).await?;
Ok(()) Ok(())
} }

View File

@@ -16,8 +16,8 @@ async fn autocomplete_shop<'a>(
ctx: Context<'_>, ctx: Context<'_>,
partial: &'a str, partial: &'a str,
) -> impl Iterator<Item = serenity::AutocompleteChoice> + use<'a> { ) -> impl Iterator<Item = serenity::AutocompleteChoice> + use<'a> {
let db = &ctx.data().database; let mut tx = ctx.data().database.begin().await.unwrap();
let balance = super::get_balance(ctx.author().id, db).await; let balance = super::get_balance(ctx.author().id, &mut *tx).await;
ITEMS.values() ITEMS.values()
.filter(move |(_, item)| item.name.contains(partial)) .filter(move |(_, item)| item.name.contains(partial))

View File

@@ -1,6 +1,6 @@
use crate::common::{Context, Error}; use crate::common::{Context, Error};
use sqlx::{PgConnection, Row}; use sqlx::PgConnection;
use poise::serenity_prelude::{EditRole, GuildId, Permissions, RoleId, UserId}; use poise::serenity_prelude::{EditRole, GuildId, Permissions, RoleId, UserId};
mod whois; mod whois;
@@ -83,18 +83,14 @@ async fn create_role(
/// Remove a row concerning a user's self role from the database /// Remove a row concerning a user's self role from the database
pub async fn remove_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<(), Error> { pub async fn remove_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<(), Error> {
sqlx::query("DELETE FROM selfroles WHERE userid = $1 AND guildid = $2") sqlx::query!("DELETE FROM selfroles WHERE userid = $1 AND guildid = $2", user.get() as i64, guild.get() as i64)
.bind(user.get() as i64)
.bind(guild.get() as i64)
.execute(db).await?; .execute(db).await?;
Ok(()) Ok(())
} }
pub async fn remove_role(role: RoleId, guild: GuildId, db: &mut PgConnection) -> Result<(), Error> { pub async fn remove_role(role: RoleId, guild: GuildId, db: &mut PgConnection) -> Result<(), Error> {
sqlx::query("DELETE FROM selfroles WHERE roleid = $1 AND guildid = $2") sqlx::query!("DELETE FROM selfroles WHERE roleid = $1 AND guildid = $2", role.get() as i64, guild.get() as i64)
.bind(role.get() as i64)
.bind(guild.get() as i64)
.execute(db).await?; .execute(db).await?;
Ok(()) Ok(())
@@ -102,10 +98,10 @@ pub async fn remove_role(role: RoleId, guild: GuildId, db: &mut PgConnection) ->
/// Replace a user's custom role with a new one /// Replace a user's custom role with a new one
pub async fn update_user_role(user: UserId, guild: GuildId, role: RoleId, db: &mut PgConnection) -> Result<(), Error> { pub async fn update_user_role(user: UserId, guild: GuildId, role: RoleId, db: &mut PgConnection) -> Result<(), Error> {
sqlx::query("INSERT INTO selfroles (userid, guildid, roleid) VALUES($1, $2, $3) ON CONFLICT (userid, guildid) DO UPDATE SET roleid = EXCLUDED.roleid") sqlx::query!(
.bind(user.get() as i64) r#"INSERT INTO selfroles (userid, guildid, roleid) VALUES($1, $2, $3)
.bind(guild.get() as i64) ON CONFLICT (userid, guildid) DO UPDATE SET roleid = EXCLUDED.roleid"#,
.bind(role.get() as i64) user.get() as i64, guild.get() as i64, role.get() as i64)
.execute(db).await?; .execute(db).await?;
Ok(()) Ok(())
@@ -113,12 +109,10 @@ pub async fn update_user_role(user: UserId, guild: GuildId, role: RoleId, db: &m
/// Get a user's personal role id from the database /// Get a user's personal role id from the database
pub async fn get_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<Option<RoleId>, Error> { pub async fn get_user_role(user: UserId, guild: GuildId, db: &mut PgConnection) -> Result<Option<RoleId>, Error> {
match sqlx::query("SELECT roleid FROM selfroles WHERE userid = $1 AND guildid = $2") match sqlx::query!("SELECT roleid FROM selfroles WHERE userid = $1 AND guildid = $2", user.get() as i64, guild.get() as i64)
.bind(user.get() as i64)
.bind(guild.get() as i64)
.fetch_one(db).await .fetch_one(db).await
{ {
Ok(row) => Ok(Some(RoleId::new(row.try_get::<i64, usize>(0)? as u64))), Ok(row) => Ok(Some(RoleId::new(row.roleid.unwrap() as u64))),
Err(sqlx::Error::RowNotFound) => Ok(None), Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => return Err(Box::new(e)), Err(e) => return Err(Box::new(e)),
} }
@@ -126,12 +120,10 @@ pub async fn get_user_role(user: UserId, guild: GuildId, db: &mut PgConnection)
/// Get a user from the role id /// Get a user from the role id
pub async fn get_user_by_role(role: RoleId, guild: GuildId, db: &mut PgConnection) -> Result<Option<UserId>, Error> { pub async fn get_user_by_role(role: RoleId, guild: GuildId, db: &mut PgConnection) -> Result<Option<UserId>, Error> {
match sqlx::query("SELECT userid FROM selfroles WHERE roleid = $1 AND guildid = $2") match sqlx::query!("SELECT userid FROM selfroles WHERE roleid = $1 AND guildid = $2", role.get() as i64, guild.get() as i64)
.bind(role.get() as i64)
.bind(guild.get() as i64)
.fetch_one(db).await .fetch_one(db).await
{ {
Ok(row) => Ok(Some(UserId::new(row.try_get::<i64, usize>(0)? as u64))), Ok(row) => Ok(Some(UserId::new(row.userid as u64))),
Err(sqlx::Error::RowNotFound) => Ok(None), Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => return Err(Box::new(e)), Err(e) => return Err(Box::new(e)),
} }

View File

@@ -3,7 +3,6 @@ use crate::common::{self, Context, Error};
use poise::serenity_prelude as serenity; use poise::serenity_prelude as serenity;
use serenity::UserId; use serenity::UserId;
use sqlx::Row;
/// Let you know who is the owner of a role. /// Let you know who is the owner of a role.
#[poise::command(slash_command, prefix_command)] #[poise::command(slash_command, prefix_command)]
@@ -11,11 +10,10 @@ pub async fn whois(ctx: Context<'_>, role: serenity::Role) -> Result<(), Error>
let db = &ctx.data().database; let db = &ctx.data().database;
if let Some(guild) = ctx.guild_id() { if let Some(guild) = ctx.guild_id() {
let user = match sqlx::query("SELECT userid FROM selfroles WHERE roleid = $1") let user = match sqlx::query!("SELECT userid FROM selfroles WHERE roleid = $1", role.id.get() as i64)
.bind(role.id.get() as i64)
.fetch_one(db).await .fetch_one(db).await
{ {
Ok(row) => UserId::new(row.try_get::<i64, usize>(0)? as u64), Ok(row) => UserId::new(row.userid as u64),
Err(sqlx::Error::RowNotFound) => { Err(sqlx::Error::RowNotFound) => {
ctx.reply("This role is not owned by anyone.").await?; ctx.reply("This role is not owned by anyone.").await?;
return Ok(()); return Ok(());