Integrate Postgres database and add sample model

This commit is contained in:
toast 2024-03-25 18:32:10 +11:00
parent a77f3c84ea
commit cdee302f4f
12 changed files with 611 additions and 263 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
target
.env
.env*

647
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ poise = "0.6.1"
sysinfo = "0.30.7"
tempfile = "3.10.1"
tokio = { version = "1.36.0", features = ["macros", "signal", "rt-multi-thread"] }
tokio-postgres = "0.7.10"
uptime_lib = "0.3.0"
[[bin]]

View File

@ -2,8 +2,22 @@ version: '3.8'
services:
bot:
container_name: rustbot
image: 'git.toast-server.net/toast/rustbot:main'
#image: 'git.toast-server.net/toast/rustbot:main'
build: .
env_file:
- .env
restart: unless-stopped
depends_on:
- db
db:
container_name: rustbot-database
image: postgres:16.2-alpine3.19
restart: unless-stopped
ports:
- 37931:5432/tcp
volumes:
- /var/lib/docker/volumes/rustbot-database:/var/lib/postgresql/data:rw
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}

2
run.sh
View File

@ -1,3 +1,3 @@
#!/bin/bash
export $(cat .env | xargs) && cargo run
export $(grep -v '^#' .env | xargs) && cargo run

View File

@ -1,3 +1,4 @@
pub mod ping;
pub mod eval;
pub mod uptime;
pub mod sample;

83
src/commands/sample.rs Normal file
View File

@ -0,0 +1,83 @@
use crate::{
Error,
models::sample::SampleData
};
use poise::CreateReply;
/// Perform sample CRUD operations in database
#[poise::command(
slash_command,
subcommands("list", "create", "update", "delete"),
subcommand_required
)]
pub async fn sample(_: poise::Context<'_, (), Error>) -> Result<(), Error> {
Ok(())
}
/// List sample data
#[poise::command(slash_command)]
pub async fn list(
ctx: poise::Context<'_, (), Error>,
#[description = "ID of the sample data"] id: u64
) -> Result<(), Error> {
let samples = SampleData::list_data(id).await?;
let mut response = String::new();
for sample in samples {
response.push_str(&format!("ID: {}\n", sample.id));
response.push_str(&format!("Text: {}\n", sample.text_val));
response.push_str(&format!("Int: {}\n", sample.int_val));
response.push_str(&format!("Boolean: {}\n\n", sample.boolean_val));
}
ctx.send(CreateReply::default()
.content(response)
).await?;
Ok(())
}
/// Create sample data
#[poise::command(slash_command)]
pub async fn create(
ctx: poise::Context<'_, (), Error>,
#[description = "Text value"] text: String,
#[description = "Int value"] int: u64,
#[description = "Boolean value"] boolean: bool
) -> Result<(), Error> {
SampleData::create_data(text, int as i64, boolean).await?;
ctx.send(CreateReply::default().content("Done!")).await?;
Ok(())
}
/// Update sample data
#[poise::command(slash_command)]
pub async fn update(
ctx: poise::Context<'_, (), Error>,
#[description = "ID of the sample data"] id: u64,
#[description = "Text value"] text: String,
#[description = "Int value"] int: u64,
#[description = "Boolean value"] boolean: bool
) -> Result<(), Error> {
SampleData::update_data(id, text, int as i64, boolean).await?;
ctx.send(CreateReply::default().content("Done!")).await?;
Ok(())
}
/// Delete sample data
#[poise::command(slash_command)]
pub async fn delete(
ctx: poise::Context<'_, (), Error>,
#[description = "ID of the sample data"] id: u64
) -> Result<(), Error> {
SampleData::delete_data(id).await?;
ctx.send(CreateReply::default().content("Done!")).await?;
Ok(())
}

View File

@ -0,0 +1,35 @@
use poise::serenity_prelude::prelude::TypeMapKey;
use tokio_postgres::{Client, NoTls, Error};
pub struct DatabaseController {
pub client: Client
}
impl TypeMapKey for DatabaseController {
type Value = DatabaseController;
}
impl DatabaseController {
pub async fn new() -> Result<DatabaseController, Error> {
let db_uri = std::env::var("DATABASE_URI").expect("Expected a \"DATABASE_URI\" in the envvar but none was found");
let (client, connection) = tokio_postgres::connect(&db_uri, NoTls).await?;
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("Connection error: {}", e);
}
});
// Sample model
client.batch_execute("
CREATE TABLE IF NOT EXISTS sample (
id BIGSERIAL PRIMARY KEY,
text_val VARCHAR(255) NOT NULL,
int_val BIGINT NOT NULL,
boolean_val BOOLEAN NOT NULL
);
").await?;
Ok(DatabaseController { client })
}
}

1
src/controllers/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod database;

View File

@ -1,4 +1,6 @@
mod commands;
mod controllers;
mod models;
mod internals;
use std::{
@ -58,13 +60,15 @@ async fn on_ready(
#[tokio::main]
async fn main() {
let token = var("DISCORD_TOKEN").expect("Expected a \"DISCORD_TOKEN\" in the envvar but none was found");
let db = controllers::database::DatabaseController::new().await.expect("Failed to connect to database");
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: vec![
commands::ping::ping(),
commands::eval::eval(),
commands::uptime::uptime()
commands::uptime::uptime(),
commands::sample::sample()
],
pre_command: |ctx| Box::pin(async move {
let get_guild_name = match ctx.guild() {
@ -87,7 +91,14 @@ async fn main() {
.setup(|ctx, ready, framework| Box::pin(on_ready(ctx, ready, framework)))
.build();
let mut client = ClientBuilder::new(token, GatewayIntents::GUILDS).framework(framework).await.expect("Error creating client");
let mut client = ClientBuilder::new(token, GatewayIntents::GUILDS)
.framework(framework)
.await.expect("Error creating client");
{
let mut data = client.data.write().await;
data.insert::<controllers::database::DatabaseController>(db);
}
if let Err(why) = client.start().await {
println!("Client error: {:?}", why);

1
src/models/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod sample;

70
src/models/sample.rs Normal file
View File

@ -0,0 +1,70 @@
use crate::controllers::database::DatabaseController;
pub struct SampleData {
pub id: i64,
pub text_val: String,
pub int_val: i64,
pub boolean_val: bool
}
impl SampleData {
pub async fn list_data(id: u64) -> Result<Vec<Self>, tokio_postgres::Error> {
let client = DatabaseController::new().await?.client;
let rows = client.query("
SELECT * FROM sample
WHERE id = $1
", &[&(id as i64)]).await?;
let mut data = Vec::new();
for row in rows {
data.push(Self {
id: row.get("id"),
text_val: row.get("text_val"),
int_val: row.get("int_val"),
boolean_val: row.get("boolean_val")
});
}
Ok(data)
}
pub async fn create_data(
text: String,
int: i64,
boolean: bool
) -> Result<(), tokio_postgres::Error> {
let client = DatabaseController::new().await?.client;
client.execute("
INSERT INTO sample (text_val, int_val, boolean_val)
VALUES ($1, $2, $3)
", &[&text, &int, &boolean]).await?;
Ok(())
}
pub async fn update_data(
id: u64,
text: String,
int: i64,
boolean: bool
) -> Result<(), tokio_postgres::Error> {
let client = DatabaseController::new().await?.client;
client.execute("
UPDATE sample
SET text_val = $1, int_val = $2, boolean_val = $3
WHERE id = $4
", &[&text, &int, &boolean, &(id as i64)]).await?;
Ok(())
}
pub async fn delete_data(id: u64) -> Result<(), tokio_postgres::Error> {
let client = DatabaseController::new().await?.client;
client.execute("
DELETE FROM sample
WHERE id = $1
", &[&(id as i64)]).await?;
Ok(())
}
}