diff --git a/migrations/2020-12-22-183017_addProgramPublishedFlag/down.sql b/migrations/2020-12-22-183017_addProgramPublishedFlag/down.sql new file mode 100644 index 0000000000000000000000000000000000000000..1aa5247d9a29707cfa1b1b2dd5a862a0d0b198d4 --- /dev/null +++ b/migrations/2020-12-22-183017_addProgramPublishedFlag/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE user_programs DROP COLUMN published; \ No newline at end of file diff --git a/migrations/2020-12-22-183017_addProgramPublishedFlag/up.sql b/migrations/2020-12-22-183017_addProgramPublishedFlag/up.sql new file mode 100644 index 0000000000000000000000000000000000000000..6dea36ddc1e236873a44e2be026864aef3b81dd8 --- /dev/null +++ b/migrations/2020-12-22-183017_addProgramPublishedFlag/up.sql @@ -0,0 +1 @@ +ALTER TABLE user_programs ADD COLUMN published INT NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/src/handler.rs b/src/handler.rs index 699a1ea1f44f02dc47db0c1625a76961866b8972..e79997c4e7f67d4cb9a2ca42e157c8e83d558d83 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -8,7 +8,7 @@ use serenity::model::channel::{Message, ReactionType}; use serenity::model::id::UserId; use serenity::model::prelude::Ready; use serenity::prelude::*; -use std::borrow::Cow; +use std::borrow::{Borrow, Cow}; use std::collections::HashMap; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -86,6 +86,25 @@ impl Handler { "LIST" => { self.interpreter_list(msg, ctx).await; } + "DIR" => { + if self.list_saved_programs(msg, ctx).await.is_none() { + msg.channel_id + .say(&ctx, "Could not get list of programs from database.") + .await + .unwrap(); + } + } + "PUBDIR" => { + if self.list_published_programs(msg, ctx).await.is_none() { + msg.channel_id + .say( + &ctx, + "Could not get list of published programs from database.", + ) + .await + .unwrap(); + } + } _ => { if let Some(name) = line.strip_prefix("SAVE ") { self.interpreter_save(name, msg, ctx).await; @@ -95,6 +114,43 @@ impl Handler { self.interpreter_load(name, msg, ctx).await; } + if let Some(name) = line.strip_prefix("PUB ") { + if self.publish_program(name, msg, ctx).await.is_none() { + msg.channel_id + .say(&ctx, format!("Could not publish {}.", name)) + .await + .unwrap(); + } + } + + if let Some(name) = line.strip_prefix("UNPUB ") { + if self.unpublish_program(name, msg, ctx).await.is_none() { + msg.channel_id + .say(&ctx, &format!("Could not unpublish {}.", name)) + .await + .unwrap(); + } + } + + if let Some(id_str) = line.strip_prefix("PUBLOAD ") { + match id_str.parse::<i32>() { + Ok(id) => { + if self.load_published_program(msg, ctx, id).await.is_none() { + msg.channel_id + .say(&ctx, format!("Could not load {}.", id)) + .await + .unwrap(); + } + } + Err(_) => { + msg.channel_id + .say(&ctx, "PUBLOAD requires a numerical ID.") + .await + .unwrap(); + } + } + } + let mut split = line.splitn(2, ' '); let first = split.next().unwrap(); if let Ok(num) = first.parse::<u32>() { @@ -139,6 +195,84 @@ impl Handler { .insert(msg.author.id, Program::new()); } + async fn list_saved_programs(&self, msg: &Message, ctx: &Context) -> Option<()> { + let program_names = + Program::list_programs_by_user(self.conn.lock().ok()?.borrow(), msg.author.id)?; + msg.channel_id + .say( + &ctx, + format!( + "You have {} programs saved:\r\n```\r\n{}\r\n```", + program_names.len(), + program_names.join("\r\n") + ), + ) + .await + .ok()?; + + Some(()) + } + + async fn list_published_programs(&self, msg: &Message, ctx: &Context) -> Option<()> { + let program_names: Vec<String> = + Program::list_published_programs(self.conn.lock().ok()?.borrow())? + .iter() + .map(|row| format!("{}\t{}", row.0, row.1)) + .collect(); + + msg.channel_id + .say( + &ctx, + format!( + "Found {} public programs:\r\n```\r\nid\tname\r\n{}\r\n```\r\n Load one with `PUBLOAD <id>`", + program_names.len(), + program_names.join("\r\n")), + ) + .await + .unwrap(); + + Some(()) + } + + async fn publish_program(&self, name: &str, msg: &Message, ctx: &Context) -> Option<()> { + Program::set_program_published(self.conn.lock().ok()?.borrow(), name, msg.author.id, true)?; + + msg.channel_id + .say(&ctx, format!("Published {}.", name)) + .await + .unwrap(); + + Some(()) + } + + async fn unpublish_program(&self, name: &str, msg: &Message, ctx: &Context) -> Option<()> { + Program::set_program_published( + self.conn.lock().ok()?.borrow(), + name, + msg.author.id, + false, + )?; + + msg.channel_id + .say(&ctx, format!("Published {}.", name)) + .await + .unwrap(); + + Some(()) + } + + async fn load_published_program(&self, msg: &Message, ctx: &Context, id: i32) -> Option<()> { + let name = get_user_programs!(&self, &msg.author.id) + .load_published_program(&self.conn.lock().ok()?.borrow(), id)?; + + msg.channel_id + .say(&ctx, format!("Loaded {} (\"{}\") into memory.", id, name)) + .await + .unwrap(); + + Some(()) + } + async fn interpreter_list(&self, msg: &Message, ctx: &Context) { msg.channel_id .say( diff --git a/src/program.rs b/src/program.rs index dd4a7959dfa1792604ec1de66649fdd3b9bad699..689e8e81499b51aa5cb5d44a7b7a2ee39878ba70 100644 --- a/src/program.rs +++ b/src/program.rs @@ -18,6 +18,65 @@ impl Program { } } + pub fn list_programs_by_user(conn: &PgConnection, user_id: UserId) -> Option<Vec<String>> { + user_programs + .filter(columns::discord_user_id.eq(BigDecimal::from_u64(*user_id.as_u64()).unwrap())) + .select(columns::name) + .get_results(conn) + .ok() + } + + pub fn list_published_programs(conn: &PgConnection) -> Option<Vec<(i32, String)>> { + user_programs + .filter(columns::published.eq(1)) + .select((columns::id, columns::name)) + .load(conn) + .ok() + } + + pub fn load_published_program(&mut self, conn: &PgConnection, id: i32) -> Option<String> { + let program: Vec<(String, String)> = user_programs + .filter(columns::id.eq(id).and(columns::published.eq(1))) + .limit(1) + .select((columns::name, columns::code)) + .get_results(conn) + .ok()?; + + if program.is_empty() { + return None; + } + + let name = &program[0].0; + let code = &program[0].1; + + self.parse_string(code)?; + + Some(name.to_string()) + } + + pub fn set_program_published( + conn: &PgConnection, + name: &str, + user_id: UserId, + published: bool, + ) -> Option<()> { + if diesel::update( + user_programs.filter( + columns::discord_user_id + .eq(BigDecimal::from_u64(*user_id.as_u64()).unwrap()) + .and(columns::name.eq(name)), + ), + ) + .set(columns::published.eq(if published { 1 } else { 0 })) + .execute(conn) + .ok()? == 1 + { + Some(()) + } else { + None + } + } + pub fn stringify(&self) -> String { let mut code: Vec<(u32, String)> = self.code.iter().map(|(a, b)| (*a, b.to_owned())).collect(); diff --git a/src/schema.rs b/src/schema.rs index 115cafc6b9e8e554f49404145309ebb6916b5957..3278e43014c6a352eb533a370c60b35245b43e10 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,8 +1,9 @@ table! { - user_programs (id) { - id -> Int4, - discord_user_id -> Numeric, - name -> Varchar, - code -> Text, - } + user_programs (id) { + id -> Int4, + discord_user_id -> Numeric, + name -> Varchar, + code -> Text, + published -> Int4, + } }