mirror of
https://github.com/System-End/plura.git
synced 2026-04-19 19:45:08 +00:00
feat: add comical amounts of logging
This commit is contained in:
parent
c6135a629d
commit
fb6f80e5b8
19 changed files with 622 additions and 249 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
|
@ -369,9 +369,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.39"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f"
|
||||
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -379,9 +379,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.39"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51"
|
||||
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -391,9 +391,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.32"
|
||||
version = "4.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
|
@ -2426,6 +2426,7 @@ dependencies = [
|
|||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"tracing-subscriber",
|
||||
|
|
@ -2950,6 +2951,7 @@ dependencies = [
|
|||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
axum = "0.8.4"
|
||||
clap = { version = "4.5.39", features = ["derive"] }
|
||||
clap = { version = "4.5.40", features = ["derive"] }
|
||||
displaydoc = "0.2.5"
|
||||
error-stack = { version = "0.5.0", features = [
|
||||
"eyre",
|
||||
|
|
@ -40,6 +40,7 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
|||
dotenvy = { git = "https://github.com/allan2/dotenvy", features = ["macros"] }
|
||||
url = "2.5.4"
|
||||
serde_json = "1.0.140"
|
||||
tower-http = { version = "0.6.6", features = ["trace"] }
|
||||
|
||||
[features]
|
||||
encrypt = ["libsqlite3-sys/bundled-sqlcipher"]
|
||||
|
|
|
|||
87
migrations/20250615174444_is_prefix_to_generic_int.sql
Normal file
87
migrations/20250615174444_is_prefix_to_generic_int.sql
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
-- Add migration script here
|
||||
-- Rename and retype is_prefix (bool) to type (interger)
|
||||
-- First, drop the trigger that references the triggers table
|
||||
DROP TRIGGER IF EXISTS ensure_system_id_update_members_table;
|
||||
|
||||
-- Create a new table with the updated schema
|
||||
CREATE TABLE triggers_new (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
-- The member that will front
|
||||
member_id INTEGER NOT NULL REFERENCES members (id),
|
||||
-- The trigger text. This will be the prefix or suffix depending on the is_prefix flag
|
||||
text TEXT NOT NULL,
|
||||
-- 0 if suffix, 1 if prefix, see rust implementation for details
|
||||
typ INTEGER NOT NULL,
|
||||
system_id INTEGER NOT NULL,
|
||||
-- Create unique constraints using the system_id and trigger type
|
||||
CONSTRAINT unique_trigger UNIQUE (system_id, text, typ)
|
||||
) STRICT;
|
||||
|
||||
-- Migrate existing data from the old table
|
||||
INSERT INTO
|
||||
triggers_new (member_id, text, typ, system_id)
|
||||
SELECT
|
||||
member_id,
|
||||
text,
|
||||
-- 0 is suffix, 1 is prefix
|
||||
is_prefix,
|
||||
system_id
|
||||
FROM
|
||||
triggers;
|
||||
|
||||
-- Recreate original state/names
|
||||
DROP TRIGGER IF EXISTS ensure_system_id;
|
||||
|
||||
DROP TRIGGER IF EXISTS ensure_system_id_update;
|
||||
|
||||
DROP TABLE triggers;
|
||||
|
||||
ALTER TABLE triggers_new
|
||||
RENAME TO triggers;
|
||||
|
||||
CREATE TRIGGER ensure_system_id BEFORE INSERT ON triggers FOR EACH ROW BEGIN
|
||||
SELECT
|
||||
RAISE (
|
||||
ABORT,
|
||||
'system_id must be the same as the system_id on member'
|
||||
)
|
||||
WHERE
|
||||
NEW.system_id != (
|
||||
SELECT
|
||||
system_id
|
||||
FROM
|
||||
members
|
||||
WHERE
|
||||
id = NEW.member_id
|
||||
);
|
||||
|
||||
END;
|
||||
|
||||
CREATE TRIGGER ensure_system_id_update BEFORE
|
||||
UPDATE ON triggers FOR EACH ROW BEGIN
|
||||
SELECT
|
||||
RAISE (
|
||||
ABORT,
|
||||
'system_id must be the same as the system_id on member'
|
||||
)
|
||||
WHERE
|
||||
NEW.system_id != (
|
||||
SELECT
|
||||
system_id
|
||||
FROM
|
||||
members
|
||||
WHERE
|
||||
id = NEW.member_id
|
||||
);
|
||||
|
||||
END;
|
||||
|
||||
CREATE TRIGGER ensure_system_id_update_members_table BEFORE
|
||||
UPDATE ON members FOR EACH ROW BEGIN
|
||||
UPDATE triggers
|
||||
SET
|
||||
system_id = NEW.system_id
|
||||
WHERE
|
||||
member_id = NEW.id;
|
||||
|
||||
END;
|
||||
|
|
@ -2,11 +2,12 @@ use std::sync::Arc;
|
|||
|
||||
use error_stack::{Result, ResultExt, report};
|
||||
use slack_morphism::prelude::*;
|
||||
use tracing::{debug, info};
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
use crate::{
|
||||
BOT_TOKEN,
|
||||
commands::members,
|
||||
fields,
|
||||
models::{
|
||||
member::{self, Member, View},
|
||||
system::{ChangeActiveMemberError, System},
|
||||
|
|
@ -53,18 +54,20 @@ pub enum Members {
|
|||
#[derive(thiserror::Error, displaydoc::Display, Debug)]
|
||||
pub enum CommandError {
|
||||
/// Error while calling the Slack API
|
||||
Slack,
|
||||
SlackApi,
|
||||
/// Error while calling the database
|
||||
Sqlx,
|
||||
}
|
||||
|
||||
impl Members {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn run(
|
||||
self,
|
||||
event: SlackCommandEvent,
|
||||
client: Arc<SlackHyperClient>,
|
||||
state: SlackClientEventsUserState,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
trace!("Running members command");
|
||||
match self {
|
||||
Self::Add => {
|
||||
let token = &BOT_TOKEN;
|
||||
|
|
@ -72,7 +75,7 @@ impl Members {
|
|||
Self::create_member(event, session).await
|
||||
}
|
||||
Self::Delete { member } => {
|
||||
info!("Deleting member {member}");
|
||||
debug!(member_id = member, "Delete member command not implemented");
|
||||
Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text("Working on it".into()),
|
||||
))
|
||||
|
|
@ -88,12 +91,14 @@ impl Members {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state), fields(system_id))]
|
||||
async fn switch_member(
|
||||
event: SlackCommandEvent,
|
||||
state: SlackClientEventsUserState,
|
||||
member_id: Option<i64>,
|
||||
base: bool,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
trace!("Switching member");
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
|
||||
|
|
@ -101,30 +106,45 @@ impl Members {
|
|||
.await
|
||||
.change_context(CommandError::Sqlx)?
|
||||
else {
|
||||
debug!("User has no system configured");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text("You don't have a system yet!".into()),
|
||||
));
|
||||
};
|
||||
|
||||
fields!(system_id = %system.id);
|
||||
debug!("Found user system");
|
||||
|
||||
let new_active_member_id = if base {
|
||||
None
|
||||
} else {
|
||||
member::Id::new(
|
||||
member_id.expect("member_id to be Some, as the clap rules require it to be."),
|
||||
)
|
||||
.validate_by_system(system.id, &user_state.db)
|
||||
.await
|
||||
.ok()
|
||||
let member_id =
|
||||
member_id.expect("member_id to be Some, as the clap rules require it to be.");
|
||||
debug!(requested_member_id = member_id, "Validating member ID");
|
||||
|
||||
member::Id::new(member_id)
|
||||
.validate_by_system(system.id, &user_state.db)
|
||||
.await
|
||||
.ok()
|
||||
};
|
||||
|
||||
debug!(target_member_id = ?new_active_member_id, "Changing active member");
|
||||
|
||||
let new_member = system
|
||||
.change_active_member(new_active_member_id, &user_state.db)
|
||||
.await;
|
||||
|
||||
let response = match new_member {
|
||||
Ok(Some(member)) => format!("Switch to member {}", member.full_name),
|
||||
Ok(None) => "Switched to base account".into(),
|
||||
Ok(Some(member)) => {
|
||||
info!(member_name = %member.full_name, member_id = %member.id, "Successfully switched to member");
|
||||
format!("Switch to member {}", member.full_name)
|
||||
}
|
||||
Ok(None) => {
|
||||
info!("Successfully switched to base account");
|
||||
"Switched to base account".into()
|
||||
}
|
||||
Err(ChangeActiveMemberError::MemberNotFound) => {
|
||||
debug!("Requested member not found in system");
|
||||
"The member you gave doesn't exist!".into()
|
||||
}
|
||||
Err(ChangeActiveMemberError::Sqlx(err)) => {
|
||||
|
|
@ -137,31 +157,37 @@ impl Members {
|
|||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state), fields(user_id, system_id))]
|
||||
async fn list_members(
|
||||
event: SlackCommandEvent,
|
||||
state: SlackClientEventsUserState,
|
||||
system: Option<String>,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
trace!("Listing all members");
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
|
||||
// If the input exists, parse it into a user ID
|
||||
// If it doesn't exist, use the user ID of the event.
|
||||
// If the user ID is invalid, return an error.
|
||||
// Theres probably a better way to write this behaviour but I'm not sure how.
|
||||
// There's probably a better way to write this behaviour but I'm not sure how.
|
||||
let Some((user_id, is_author)) = system.map_or_else(
|
||||
|| Some((user::Id::new(event.user_id), true)),
|
||||
|u| user::parse_slack_user_id(&u).map(|id| (id, false)),
|
||||
) else {
|
||||
debug!("Invalid user ID provided in system parameter");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text("Invalid user ID".into()),
|
||||
));
|
||||
};
|
||||
|
||||
fields!(user_id = %user_id.clone());
|
||||
|
||||
let Some(system) = System::fetch_by_user_id(&user_state.db, &user_id)
|
||||
.await
|
||||
.change_context(CommandError::Sqlx)?
|
||||
else {
|
||||
debug!(target_user_id = %user_id, is_self = is_author, "Target user has no system");
|
||||
return if is_author {
|
||||
Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text("You don't have a system yet!".into()),
|
||||
|
|
@ -173,11 +199,15 @@ impl Members {
|
|||
};
|
||||
};
|
||||
|
||||
fields!(system_id = %system.id);
|
||||
|
||||
let members = system
|
||||
.get_members(&user_state.db)
|
||||
.await
|
||||
.change_context(CommandError::Sqlx)?;
|
||||
|
||||
debug!(member_count = members.len(), "Retrieved system members");
|
||||
|
||||
let member_blocks = members
|
||||
.into_iter()
|
||||
.map(|member| {
|
||||
|
|
@ -213,11 +243,14 @@ impl Members {
|
|||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state), fields(user_id = %event.user_id, system_id, member_id))]
|
||||
async fn member_info(
|
||||
event: SlackCommandEvent,
|
||||
state: &SlackClientEventsUserState,
|
||||
member_id: i64,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
trace!("Running member info command");
|
||||
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
let member_id = member::Id::new(member_id);
|
||||
|
|
@ -227,6 +260,7 @@ impl Members {
|
|||
.change_context(CommandError::Sqlx)?
|
||||
.map(|system| system.id)
|
||||
else {
|
||||
debug!("User has no system configured");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text(
|
||||
"You don't have a system yet! Make one with `/system create <name>`".into(),
|
||||
|
|
@ -234,16 +268,22 @@ impl Members {
|
|||
));
|
||||
};
|
||||
|
||||
fields!(system_id = %system_id);
|
||||
|
||||
let Some(member) = Member::fetch_by_and_trust_id(system_id, member_id, &user_state.db)
|
||||
.await
|
||||
.change_context(CommandError::Sqlx)?
|
||||
else {
|
||||
debug!("Member not found");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new()
|
||||
.with_text("Member not found. Make sure you used the correct ID".into()),
|
||||
));
|
||||
};
|
||||
|
||||
fields!(member_id = %member.id);
|
||||
debug!("Member found");
|
||||
|
||||
let fields = [
|
||||
Some(md!("Display Name: {}", member.display_name)),
|
||||
Some(md!("Member ID: {}", member.id)),
|
||||
|
|
@ -270,31 +310,36 @@ impl Members {
|
|||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, session), fields(view_id))]
|
||||
async fn create_member(
|
||||
event: SlackCommandEvent,
|
||||
session: SlackClientSession<'_, SlackClientHyperHttpsConnector>,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
trace!("Running member creation command");
|
||||
let view = View::create_add_view();
|
||||
|
||||
let view = session
|
||||
.views_open(&SlackApiViewsOpenRequest::new(event.trigger_id, view))
|
||||
.await
|
||||
.attach_printable("Error opening view")
|
||||
.change_context(CommandError::Slack)?;
|
||||
.change_context(CommandError::SlackApi)?;
|
||||
|
||||
debug!("Opened view: {:#?}", view);
|
||||
info!(view_id = %view.view.state_params.id, "Successfully opened member creation view");
|
||||
|
||||
Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text("View opened!".into()),
|
||||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, session, state), fields(user_id = %event.user_id, trigger_id = %event.trigger_id))]
|
||||
async fn edit_member(
|
||||
event: SlackCommandEvent,
|
||||
session: SlackClientSession<'_, SlackClientHyperHttpsConnector>,
|
||||
state: &SlackClientEventsUserState,
|
||||
member_id: i64,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
trace!("Running member edit command");
|
||||
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
let user_id = user::Id::new(event.user_id);
|
||||
|
|
@ -305,6 +350,7 @@ impl Members {
|
|||
.change_context(CommandError::Sqlx)?
|
||||
.map(|system| system.id)
|
||||
else {
|
||||
debug!("User has no system configured");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text(
|
||||
"You don't have a system yet! Make one with `/system create <name>`".into(),
|
||||
|
|
@ -333,9 +379,9 @@ impl Members {
|
|||
))
|
||||
.await
|
||||
.attach_printable("Error opening view")
|
||||
.change_context(CommandError::Slack)?;
|
||||
.change_context(CommandError::SlackApi)?;
|
||||
|
||||
debug!("Opened view: {:#?}", view);
|
||||
info!(view_id = %view.view.state_params.id, member_id = %member_id, "Successfully opened member edit view");
|
||||
|
||||
Ok(SlackCommandEventResponse::new(SlackMessageContent::new()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,17 @@ mod members;
|
|||
mod system;
|
||||
mod triggers;
|
||||
use axum::{Extension, Json};
|
||||
use clap::Parser;
|
||||
use clap::{Parser, error::ErrorKind};
|
||||
use error_stack::ResultExt;
|
||||
use members::Members;
|
||||
|
||||
use slack_morphism::prelude::*;
|
||||
use system::System;
|
||||
use tracing::{debug, error};
|
||||
use tracing::{Level, debug, error, trace};
|
||||
use triggers::Triggers;
|
||||
|
||||
use crate::fields;
|
||||
|
||||
#[derive(clap::Parser, Debug)]
|
||||
#[command(color(clap::ColorChoice::Never))]
|
||||
enum Command {
|
||||
|
|
@ -25,6 +27,7 @@ enum Command {
|
|||
}
|
||||
|
||||
impl Command {
|
||||
#[tracing::instrument(level = Level::DEBUG, skip(event, client, state), fields(runner_user_id = %event.user_id, runner_channel_id = %event.channel_id, runner_channel_name = ?event.channel_name, trigger_id = %event.trigger_id))]
|
||||
pub async fn run(
|
||||
self,
|
||||
event: SlackCommandEvent,
|
||||
|
|
@ -35,29 +38,30 @@ impl Command {
|
|||
Self::Members(members) => members
|
||||
.run(event, client, state)
|
||||
.await
|
||||
.attach_printable("Failed to run members command")
|
||||
.change_context(CommandError::Command),
|
||||
.change_context(CommandError::Members),
|
||||
Self::System(system) => system
|
||||
.run(event, client, state)
|
||||
.await
|
||||
.attach_printable("Failed to run system command")
|
||||
.change_context(CommandError::Command),
|
||||
.change_context(CommandError::System),
|
||||
Self::Triggers(triggers) => triggers
|
||||
.run(event, client, state)
|
||||
.await
|
||||
.attach_printable("Failed to run triggers command")
|
||||
.change_context(CommandError::Command),
|
||||
.change_context(CommandError::Triggers),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, displaydoc::Display, Debug)]
|
||||
enum CommandError {
|
||||
/// Error running the command
|
||||
Command,
|
||||
/// Error running the members command
|
||||
Members,
|
||||
/// Error running the triggers command
|
||||
Triggers,
|
||||
/// Error running the system command
|
||||
System,
|
||||
}
|
||||
|
||||
// TODO: figure out error handling
|
||||
// TO-DO: figure out error handling
|
||||
#[tracing::instrument(skip(environment, event))]
|
||||
pub async fn process_command_event(
|
||||
Extension(environment): Extension<Arc<SlackHyperListenerEnvironment>>,
|
||||
|
|
@ -69,7 +73,7 @@ pub async fn process_command_event(
|
|||
match command_event_callback(event, client, state).await {
|
||||
Ok(response) => Json(response),
|
||||
Err(e) => {
|
||||
error!("Error processing command event: {:#?}", e);
|
||||
error!(error = ?e, "Error processing command event");
|
||||
Json(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new()
|
||||
.with_text("Error processing command! Logged to developers".into()),
|
||||
|
|
@ -78,12 +82,13 @@ pub async fn process_command_event(
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = Level::TRACE, skip(client, state), fields(command))]
|
||||
async fn command_event_callback(
|
||||
event: SlackCommandEvent,
|
||||
client: Arc<SlackHyperClient>,
|
||||
state: SlackClientEventsUserState,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
debug!("Received command: {:?}", event.command);
|
||||
trace!(command = ?event.command, "Received command");
|
||||
|
||||
let formatted_command = event.command.0.trim_start_matches('/');
|
||||
let formatted = event.text.as_ref().map_or_else(
|
||||
|
|
@ -91,22 +96,21 @@ async fn command_event_callback(
|
|||
|text| format!("slack-system-bot {formatted_command} {text}"),
|
||||
);
|
||||
|
||||
debug!("Formatted command: {formatted}");
|
||||
fields!(command = &formatted);
|
||||
|
||||
let parser = Command::try_parse_from(formatted.split_whitespace());
|
||||
|
||||
match parser {
|
||||
Ok(parser) => {
|
||||
debug!("Parsed command: {:?}", parser);
|
||||
debug!(?parser, "Parsed command. Running...");
|
||||
let result = parser.run(event, client, state).await;
|
||||
match result {
|
||||
Ok(res) => {
|
||||
debug!("Command {} executed successfully", formatted);
|
||||
debug!("Command executed successfully");
|
||||
Ok(res)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error running command {formatted}");
|
||||
error!("{e:?}");
|
||||
error!(error = ?e, "Error running command");
|
||||
Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text(
|
||||
"Error running command! TODO: show error info on slack".into(),
|
||||
|
|
@ -116,6 +120,15 @@ async fn command_event_callback(
|
|||
}
|
||||
}
|
||||
Err(error) => {
|
||||
if !matches!(
|
||||
error.kind(),
|
||||
ErrorKind::DisplayHelp
|
||||
| ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
|
||||
| ErrorKind::DisplayVersion
|
||||
) {
|
||||
debug!(error = ?error, "Error parsing command. Most likely user's fault");
|
||||
}
|
||||
|
||||
let formatted = error.render();
|
||||
Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text(formatted.to_string()),
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ use error_stack::{Result, ResultExt};
|
|||
use oauth2::CsrfToken;
|
||||
use slack_morphism::prelude::*;
|
||||
use tokio::runtime::Handle;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::{
|
||||
fields,
|
||||
models::{system, user},
|
||||
oauth::create_oauth_client,
|
||||
};
|
||||
|
|
@ -39,6 +40,7 @@ pub enum CommandError {
|
|||
}
|
||||
|
||||
impl System {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn run(
|
||||
self,
|
||||
event: SlackCommandEvent,
|
||||
|
|
@ -55,13 +57,14 @@ impl System {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(user_id, system_id))]
|
||||
async fn get_system_info(
|
||||
event: SlackCommandEvent,
|
||||
client: Arc<SlackHyperClient>,
|
||||
state: SlackClientEventsUserState,
|
||||
user: Option<String>,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
debug!("Getting system info");
|
||||
trace!("Getting system info");
|
||||
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
|
|
@ -82,11 +85,16 @@ impl System {
|
|||
));
|
||||
};
|
||||
|
||||
fields!(user_id = %&user_id);
|
||||
trace!("Mapped user ID");
|
||||
|
||||
let system = system::System::fetch_by_user_id(&user_state.db, &user_id)
|
||||
.await
|
||||
.change_context(CommandError::Sqlx)?;
|
||||
|
||||
if let Some(system) = system {
|
||||
fields!(system_id = %system.id);
|
||||
debug!("Fetched system");
|
||||
let fronting_member = system
|
||||
.active_member(&user_state.db)
|
||||
.await
|
||||
|
|
@ -108,6 +116,7 @@ impl System {
|
|||
]),
|
||||
))
|
||||
} else {
|
||||
debug!("User does not have a system");
|
||||
Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_blocks(slack_blocks![some_into(
|
||||
SlackSectionBlock::new().with_text(md!("This user doesn't have a system!"))
|
||||
|
|
@ -116,12 +125,13 @@ impl System {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state), fields(system_id))]
|
||||
async fn edit_system_name(
|
||||
event: SlackCommandEvent,
|
||||
state: SlackClientEventsUserState,
|
||||
name: String,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
debug!("Editing system name {name}");
|
||||
trace!("Editing system name");
|
||||
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
|
|
@ -141,40 +151,35 @@ impl System {
|
|||
));
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE systems
|
||||
SET name = $1
|
||||
WHERE id = $2
|
||||
"#,
|
||||
name,
|
||||
system_id
|
||||
)
|
||||
.execute(&user_state.db)
|
||||
.await
|
||||
.change_context(CommandError::Sqlx)?;
|
||||
fields!(system_id = %system_id);
|
||||
|
||||
system_id
|
||||
.rename(&name, &user_state.db)
|
||||
.await
|
||||
.change_context(CommandError::Sqlx)?;
|
||||
|
||||
Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text("Successfully updated system name!".into()),
|
||||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state))]
|
||||
async fn create_system(
|
||||
event: SlackCommandEvent,
|
||||
state: SlackClientEventsUserState,
|
||||
name: String,
|
||||
) -> Result<SlackCommandEventResponse, CommandError> {
|
||||
debug!("Creating system {name}");
|
||||
trace!("Creating system");
|
||||
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
let user_id = user::Id::new(event.user_id);
|
||||
|
||||
// todo: somehow remove this clone with cleaner code in the future`
|
||||
if system::System::fetch_by_user_id(&user_state.db, &user::Id::new(event.user_id.clone()))
|
||||
if let Some(system) = system::System::fetch_by_user_id(&user_state.db, &user_id)
|
||||
.await
|
||||
.change_context(CommandError::Sqlx)?
|
||||
.is_some()
|
||||
{
|
||||
debug!(system_id = %system.id, "User already has a system");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text("You already have a system! If you need to reauthenticate, run /system reauth. If you need to change your system name, run /system rename".into()),
|
||||
));
|
||||
|
|
@ -182,11 +187,11 @@ impl System {
|
|||
|
||||
let oauth_client = create_oauth_client();
|
||||
|
||||
// note: we aren't doing PKCE since this is only ran on a trusted server
|
||||
// Note: we aren't doing PKCE since this is only ran on a trusted server
|
||||
|
||||
let (auth_url, csrf_token) = oauth_client
|
||||
.authorize_url(CsrfToken::new_random)
|
||||
// so we get a regular token as well. Required by oauth2 for some reason
|
||||
// So we get a regular token as well. Required by oauth2 for some reason
|
||||
.add_extra_param("scope", "commands")
|
||||
.add_extra_param("user_scope", "users.profile:read,chat:write")
|
||||
.url();
|
||||
|
|
@ -199,7 +204,7 @@ impl System {
|
|||
VALUES ($1, $2, $3)
|
||||
"#,
|
||||
name,
|
||||
event.user_id.0,
|
||||
user_id.id,
|
||||
secret
|
||||
)
|
||||
.execute(&user_state.db)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use slack_morphism::prelude::*;
|
|||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
BOT_TOKEN,
|
||||
BOT_TOKEN, fields,
|
||||
models::{
|
||||
member::{self, Member},
|
||||
system::System,
|
||||
|
|
@ -46,6 +46,7 @@ pub enum CommandError {
|
|||
}
|
||||
|
||||
impl Triggers {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn run(
|
||||
self,
|
||||
event: SlackCommandEvent,
|
||||
|
|
@ -68,6 +69,7 @@ impl Triggers {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state, session), fields(system_id, member_id))]
|
||||
async fn create_trigger(
|
||||
event: SlackCommandEvent,
|
||||
state: &SlackClientEventsUserState,
|
||||
|
|
@ -84,6 +86,7 @@ impl Triggers {
|
|||
.change_context(CommandError::Sqlx)?
|
||||
.map(|system| system.id)
|
||||
else {
|
||||
debug!("User does not have a system");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text(
|
||||
"You don't have a system yet! Make one with `/system create <name>`".into(),
|
||||
|
|
@ -91,18 +94,23 @@ impl Triggers {
|
|||
));
|
||||
};
|
||||
|
||||
fields!(system_id = %system_id);
|
||||
|
||||
let Some(member_id) = Member::fetch_by_and_trust_id(system_id, member_id, &user_state.db)
|
||||
.await
|
||||
.change_context(CommandError::Sqlx)?
|
||||
.map(|member| member.id)
|
||||
else {
|
||||
debug!("Member not found");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new()
|
||||
.with_text("Member not found. Make sure you used the correct ID".into()),
|
||||
));
|
||||
};
|
||||
|
||||
let view = trigger::View::new(String::new(), true).create_add_view(member_id);
|
||||
fields!(member_id = %member_id);
|
||||
|
||||
let view = trigger::View::default().create_add_view(member_id);
|
||||
let view = session
|
||||
.views_open(&SlackApiViewsOpenRequest::new(
|
||||
event.trigger_id.clone(),
|
||||
|
|
@ -112,11 +120,12 @@ impl Triggers {
|
|||
.attach_printable("Error opening view")
|
||||
.change_context(CommandError::Slack)?;
|
||||
|
||||
debug!("Opened view: {:#?}", view);
|
||||
debug!(?view, "Opened view");
|
||||
|
||||
Ok(SlackCommandEventResponse::new(SlackMessageContent::new()))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state), fields(system_id))]
|
||||
pub async fn delete_trigger(
|
||||
event: SlackCommandEvent,
|
||||
state: &SlackClientEventsUserState,
|
||||
|
|
@ -139,11 +148,14 @@ impl Triggers {
|
|||
));
|
||||
};
|
||||
|
||||
fields!(system_id = %system_id);
|
||||
|
||||
// Validate the trigger belongs to the user's system
|
||||
let Ok(trigger_id) = trigger_id
|
||||
.validate_by_system(system_id, &user_state.db)
|
||||
.await
|
||||
else {
|
||||
debug!("Trigger not found");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new()
|
||||
.with_text("Trigger not found. Make sure you used the correct ID".into()),
|
||||
|
|
@ -161,6 +173,7 @@ impl Triggers {
|
|||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state), fields(system_id))]
|
||||
pub async fn list_triggers(
|
||||
event: SlackCommandEvent,
|
||||
state: &SlackClientEventsUserState,
|
||||
|
|
@ -175,6 +188,7 @@ impl Triggers {
|
|||
.change_context(CommandError::Sqlx)?
|
||||
.map(|system| system.id)
|
||||
else {
|
||||
debug!("User doesn't have a system");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text(
|
||||
"You don't have a system yet! Make one with `/system create <name>`".into(),
|
||||
|
|
@ -182,6 +196,8 @@ impl Triggers {
|
|||
));
|
||||
};
|
||||
|
||||
fields!(system_id = %system_id);
|
||||
|
||||
let triggers = if let Some(member_id) = member_id {
|
||||
let member_id = member::Id::new(member_id);
|
||||
|
||||
|
|
@ -190,12 +206,15 @@ impl Triggers {
|
|||
.validate_by_system(system_id, &user_state.db)
|
||||
.await
|
||||
else {
|
||||
debug!("Member not found");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new()
|
||||
.with_text("Member not found. Make sure you used the correct ID".into()),
|
||||
));
|
||||
};
|
||||
|
||||
fields!(member_id = %member_id);
|
||||
|
||||
member_id
|
||||
.fetch_triggers(&user_state.db)
|
||||
.await
|
||||
|
|
@ -208,26 +227,21 @@ impl Triggers {
|
|||
};
|
||||
|
||||
if triggers.is_empty() {
|
||||
debug!("No triggers found");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text("No triggers found.".into()),
|
||||
));
|
||||
}
|
||||
|
||||
debug!(len = triggers.len(), "Found triggers");
|
||||
|
||||
let trigger_blocks = triggers
|
||||
.into_iter()
|
||||
.map(|trigger| {
|
||||
let fields = vec![
|
||||
md!("Trigger ID: {}", trigger.id),
|
||||
md!("Member ID: {}", trigger.member_id),
|
||||
md!(
|
||||
"{}: {}",
|
||||
if trigger.is_prefix {
|
||||
"Prefix"
|
||||
} else {
|
||||
"Suffix"
|
||||
},
|
||||
trigger.text
|
||||
),
|
||||
md!("{}: {}", trigger.typ, trigger.text),
|
||||
];
|
||||
|
||||
SlackSectionBlock::new()
|
||||
|
|
@ -242,6 +256,7 @@ impl Triggers {
|
|||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state, session), fields(system_id))]
|
||||
pub async fn edit_trigger(
|
||||
event: SlackCommandEvent,
|
||||
state: &SlackClientEventsUserState,
|
||||
|
|
@ -258,6 +273,7 @@ impl Triggers {
|
|||
.change_context(CommandError::Sqlx)?
|
||||
.map(|system| system.id)
|
||||
else {
|
||||
debug!("User does not have a system");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new().with_text(
|
||||
"You don't have a system yet! Make one with `/system create <name>`".into(),
|
||||
|
|
@ -265,6 +281,8 @@ impl Triggers {
|
|||
));
|
||||
};
|
||||
|
||||
fields!(system_id = %system_id);
|
||||
|
||||
// Validate the trigger belongs to the user's system
|
||||
let Ok(trigger_id) = trigger_id
|
||||
.validate_by_system(system_id, &user_state.db)
|
||||
|
|
@ -276,12 +294,15 @@ impl Triggers {
|
|||
));
|
||||
};
|
||||
|
||||
fields!(trigger_id = %trigger_id);
|
||||
|
||||
// Fetch the trigger to edit
|
||||
let trigger = trigger::Trigger::fetch_by_id(trigger_id, &user_state.db)
|
||||
.await
|
||||
.change_context(CommandError::Sqlx)?;
|
||||
|
||||
let Some(trigger) = trigger else {
|
||||
debug!("Trigger not found");
|
||||
return Ok(SlackCommandEventResponse::new(
|
||||
SlackMessageContent::new()
|
||||
.with_text("Trigger not found. Make sure you used the correct ID".into()),
|
||||
|
|
@ -299,7 +320,7 @@ impl Triggers {
|
|||
.attach_printable("Error opening view")
|
||||
.change_context(CommandError::Slack)?;
|
||||
|
||||
debug!("Opened view: {:#?}", view);
|
||||
debug!(?view, "Opened view");
|
||||
|
||||
Ok(SlackCommandEventResponse::new(SlackMessageContent::new()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,49 @@
|
|||
use std::{convert::Infallible, sync::Arc};
|
||||
|
||||
use axum::{Extension, body::Bytes, http::Response};
|
||||
use error_stack::ResultExt;
|
||||
use error_stack::{Result, ResultExt};
|
||||
use http_body_util::{BodyExt, Empty, Full, combinators::BoxBody};
|
||||
use slack_morphism::prelude::*;
|
||||
use sqlx::SqlitePool;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
use crate::{
|
||||
BOT_TOKEN,
|
||||
BOT_TOKEN, fields,
|
||||
models::{
|
||||
member::{Member, TriggeredMember},
|
||||
message::MessageLog,
|
||||
system::System,
|
||||
trigger::Type,
|
||||
user,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(thiserror::Error, displaydoc::Display, Debug)]
|
||||
pub enum RewriteMessageError {
|
||||
/// Error while posting a message to Slack
|
||||
PostMessage,
|
||||
/// Error while deleting a message from Slack
|
||||
DeleteMessage,
|
||||
/// Error while serializing custom image blocks
|
||||
SerializeImageBlocks,
|
||||
/// Error while saving message log to database
|
||||
MessageLog,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, displaydoc::Display, Debug)]
|
||||
pub enum PushEventError {
|
||||
/// Error while interacting with the Slack API
|
||||
SlackApi,
|
||||
/// Error while fetching system information from database
|
||||
SystemFetch,
|
||||
/// Error while fetching member information from database
|
||||
MemberFetch,
|
||||
/// Error while attempting to change the active member
|
||||
MemberChange,
|
||||
/// Error while attempting to rewrite the message
|
||||
MessageRewrite,
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(environment, event))]
|
||||
pub async fn process_push_event(
|
||||
Extension(environment): Extension<Arc<SlackHyperListenerEnvironment>>,
|
||||
|
|
@ -44,11 +71,12 @@ pub async fn process_push_event(
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(event, state, client))]
|
||||
async fn push_event_callback(
|
||||
event: SlackPushEventCallback,
|
||||
client: Arc<SlackHyperClient>,
|
||||
state: SlackClientEventsUserState,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
) -> Result<(), PushEventError> {
|
||||
match event.event {
|
||||
SlackEventCallbackBody::Message(message_event)
|
||||
if message_event
|
||||
|
|
@ -56,14 +84,14 @@ async fn push_event_callback(
|
|||
.as_ref()
|
||||
.is_some_and(|subtype| *subtype == SlackMessageEventType::MessageDeleted) =>
|
||||
{
|
||||
fields!(event_type = ?SlackMessageEventType::MessageDeleted);
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
|
||||
MessageLog::delete_by_message_id(message_event.deleted_ts.unwrap().0, &user_state.db)
|
||||
.await
|
||||
.attach_printable("Failed to delete message log")?;
|
||||
|
||||
Ok(())
|
||||
.change_context(PushEventError::SlackApi)
|
||||
.attach_printable("Failed to delete message log")
|
||||
}
|
||||
SlackEventCallbackBody::Message(message_event)
|
||||
if message_event.subtype.is_none()
|
||||
|
|
@ -72,83 +100,117 @@ async fn push_event_callback(
|
|||
.as_ref()
|
||||
.is_some_and(|subtype| *subtype == SlackMessageEventType::MessageChanged) =>
|
||||
{
|
||||
debug!("Received message event!");
|
||||
trace!("Message: {:?}", message_event);
|
||||
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
|
||||
let Some(user_id) = message_event.sender.user.map(user::Id::new) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(mut system) = System::fetch_by_user_id(&user_state.db, &user_id).await? else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(ref channel_id) = message_event.origin.channel else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let Some(content) = message_event.content else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Some(ref message_content) = content.text {
|
||||
let Some(member) = system
|
||||
.fetch_triggered_member(&user_state.db, message_content)
|
||||
.await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
debug!("Triggered member: {:#?}", member);
|
||||
|
||||
if system.trigger_changes_active_member {
|
||||
system
|
||||
.change_active_member(Some(member.id), &user_state.db)
|
||||
.await?;
|
||||
}
|
||||
|
||||
rewrite_message(
|
||||
&client,
|
||||
channel_id,
|
||||
message_event.origin.ts,
|
||||
content,
|
||||
member,
|
||||
&system,
|
||||
&user_state.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// No triggers ran, so check if there's any actively fronting member
|
||||
if let Some(member_id) = system.active_member_id {
|
||||
let Some(member) = Member::fetch_by_id(member_id, &user_state.db).await? else {
|
||||
error!("Active member not found. This should not happen.");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
rewrite_message(
|
||||
&client,
|
||||
channel_id,
|
||||
message_event.origin.ts,
|
||||
content,
|
||||
member.into(),
|
||||
&system,
|
||||
&user_state.db,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
handle_message(message_event, &client, &state).await
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn handle_message(
|
||||
message_event: SlackMessageEvent,
|
||||
client: &SlackHyperClient,
|
||||
state: &SlackClientEventsUserState,
|
||||
) -> error_stack::Result<(), PushEventError> {
|
||||
fields!(event_type = ?message_event.subtype);
|
||||
debug!("Received message event!");
|
||||
|
||||
let states = state.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
|
||||
let Some(user_id) = message_event.sender.user.map(user::Id::new) else {
|
||||
debug!("Failed to get user ID");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
fields!(user_id = ?&user_id);
|
||||
|
||||
let Some(mut system) = System::fetch_by_user_id(&user_state.db, &user_id)
|
||||
.await
|
||||
.change_context(PushEventError::SystemFetch)?
|
||||
else {
|
||||
debug!("Failed to fetch system");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
fields!(system_id = %&system.id);
|
||||
|
||||
let Some(ref channel_id) = message_event.origin.channel else {
|
||||
debug!("Failed to get channel ID");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
fields!(channel_id = %&channel_id);
|
||||
|
||||
let Some(content) = message_event.content else {
|
||||
debug!("Failed to get message content");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Some(ref message_content) = content.text {
|
||||
let Some(member) = system
|
||||
.fetch_triggered_member(&user_state.db, message_content)
|
||||
.await
|
||||
.change_context(PushEventError::MemberFetch)?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
fields!(member = ?&member);
|
||||
debug!("Member triggered");
|
||||
|
||||
if system.trigger_changes_active_member {
|
||||
system
|
||||
.change_active_member(Some(member.id), &user_state.db)
|
||||
.await
|
||||
.change_context(PushEventError::MemberChange)?;
|
||||
}
|
||||
|
||||
rewrite_message(
|
||||
client,
|
||||
channel_id,
|
||||
message_event.origin.ts,
|
||||
content,
|
||||
member,
|
||||
&system,
|
||||
&user_state.db,
|
||||
)
|
||||
.await
|
||||
.change_context(PushEventError::MessageRewrite)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!("Member not triggered");
|
||||
|
||||
// No triggers ran, so check if there's any actively fronting member
|
||||
if let Some(member_id) = system.active_member_id {
|
||||
fields!(member = %&member_id);
|
||||
let Some(member) = Member::fetch_by_id(member_id, &user_state.db)
|
||||
.await
|
||||
.change_context(PushEventError::MemberFetch)?
|
||||
else {
|
||||
error!("Active member not found. This should not happen.");
|
||||
return Ok(());
|
||||
};
|
||||
fields!(member = ?&member);
|
||||
|
||||
rewrite_message(
|
||||
client,
|
||||
channel_id,
|
||||
message_event.origin.ts,
|
||||
content,
|
||||
member.into(),
|
||||
&system,
|
||||
&user_state.db,
|
||||
)
|
||||
.await
|
||||
.change_context(PushEventError::MemberFetch)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(client, db, system), fields(system_id = %system.id))]
|
||||
async fn rewrite_message(
|
||||
client: &SlackHyperClient,
|
||||
channel_id: &SlackChannelId,
|
||||
|
|
@ -157,8 +219,7 @@ async fn rewrite_message(
|
|||
member: TriggeredMember,
|
||||
system: &System,
|
||||
db: &SqlitePool,
|
||||
// TODO: better error handling/custom error enum
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
) -> error_stack::Result<(), RewriteMessageError> {
|
||||
let token = SlackApiToken::new(system.slack_oauth_token.expose().into())
|
||||
.with_token_type(SlackApiTokenType::User);
|
||||
let user_session = client.open_session(&token);
|
||||
|
|
@ -227,7 +288,8 @@ async fn rewrite_message(
|
|||
let custom_image_blocks = custom_image_blocks
|
||||
.into_iter()
|
||||
.map(serde_json::to_value)
|
||||
.collect::<Result<Vec<serde_json::Value>, serde_json::Error>>()?;
|
||||
.collect::<std::result::Result<Vec<serde_json::Value>, serde_json::Error>>()
|
||||
.change_context(RewriteMessageError::SerializeImageBlocks)?;
|
||||
|
||||
blocks.extend(custom_image_blocks);
|
||||
|
||||
|
|
@ -239,18 +301,18 @@ async fn rewrite_message(
|
|||
Some(&CHAT_POST_MESSAGE_SPECIAL_LIMIT_RATE_CTL),
|
||||
)
|
||||
.await
|
||||
.attach_printable("Error rewriting message")?;
|
||||
.change_context(RewriteMessageError::PostMessage)?;
|
||||
|
||||
MessageLog::insert(member.id, res.ts, db)
|
||||
.await
|
||||
.attach_printable("Could not insert message log")?;
|
||||
.change_context(RewriteMessageError::MessageLog)?;
|
||||
|
||||
user_session
|
||||
.chat_delete(
|
||||
&SlackApiChatDeleteRequest::new(channel_id.clone(), message_id).with_as_user(true),
|
||||
)
|
||||
.await
|
||||
.attach_printable("Error deleting message")?;
|
||||
.change_context(RewriteMessageError::DeleteMessage)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -259,12 +321,17 @@ fn rewrite_content(content: &mut SlackMessageContent, member: &TriggeredMember)
|
|||
debug!("Rewriting message content");
|
||||
|
||||
if let Some(text) = &mut content.text {
|
||||
if member.is_prefix {
|
||||
if let Some(new_text) = text.strip_prefix(&member.trigger_text) {
|
||||
*text = new_text.to_string();
|
||||
match member.typ {
|
||||
Type::Prefix => {
|
||||
if let Some(new_text) = text.strip_prefix(&member.trigger_text) {
|
||||
*text = new_text.to_string();
|
||||
}
|
||||
}
|
||||
Type::Suffix => {
|
||||
if let Some(new_text) = text.strip_suffix(&member.trigger_text) {
|
||||
*text = new_text.to_string();
|
||||
}
|
||||
}
|
||||
} else if let Some(new_text) = text.strip_suffix(&member.trigger_text) {
|
||||
*text = new_text.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,11 +340,11 @@ fn rewrite_content(content: &mut SlackMessageContent, member: &TriggeredMember)
|
|||
if let SlackBlock::RichText(richtext) = block {
|
||||
let elements = richtext["elements"].as_array_mut().unwrap();
|
||||
let len = elements.len();
|
||||
// the first and last elements would have the prefix and suffix respectively, so we can filter them
|
||||
// The first and last elements would have the prefix and suffix respectively, so we can filter them
|
||||
let first = elements.get_mut(0).unwrap();
|
||||
|
||||
if let Some(first_text) = first.pointer_mut("/elements/0/text") {
|
||||
if member.is_prefix {
|
||||
if member.typ == Type::Prefix {
|
||||
if let Some(new_text) = first_text
|
||||
.as_str()
|
||||
.and_then(|text| text.strip_prefix(&member.trigger_text))
|
||||
|
|
@ -291,7 +358,7 @@ fn rewrite_content(content: &mut SlackMessageContent, member: &TriggeredMember)
|
|||
let last = elements.get_mut(len - 1).unwrap();
|
||||
|
||||
if let Some(last_text) = last.pointer_mut("/elements/0/text") {
|
||||
if !member.is_prefix {
|
||||
if member.typ == Type::Suffix {
|
||||
if let Some(new_text) = last_text
|
||||
.as_str()
|
||||
.and_then(|text| text.strip_suffix(&member.trigger_text))
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use error_stack::{bail, Result, ResultExt};
|
||||
use error_stack::{Result, ResultExt, bail};
|
||||
use slack_morphism::prelude::*;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{
|
||||
BOT_TOKEN, fields,
|
||||
models::{
|
||||
member,
|
||||
Trusted, member,
|
||||
system::System,
|
||||
user::{self, State},
|
||||
Trusted,
|
||||
},
|
||||
BOT_TOKEN,
|
||||
};
|
||||
|
||||
#[derive(thiserror::Error, displaydoc::Display, Debug)]
|
||||
|
|
@ -23,12 +23,14 @@ pub enum Error {
|
|||
NoSystem,
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(view_state, client, user_state), fields(system_id))]
|
||||
pub async fn create_member(
|
||||
view_state: SlackViewState,
|
||||
client: &SlackHyperClient,
|
||||
user_state: &State,
|
||||
user_id: user::Id<Trusted>,
|
||||
) -> Result<(), Error> {
|
||||
trace!("Creating member");
|
||||
let data = member::View::try_from(view_state).change_context(Error::ParsingView)?;
|
||||
|
||||
let Some(system_id) = System::fetch_by_user_id(&user_state.db, &user_id)
|
||||
|
|
@ -40,6 +42,8 @@ pub async fn create_member(
|
|||
bail!(Error::NoSystem);
|
||||
};
|
||||
|
||||
fields!(system_id = %system_id);
|
||||
|
||||
let id = data
|
||||
.add(system_id, &user_state.db)
|
||||
.await
|
||||
|
|
@ -69,6 +73,7 @@ pub async fn create_member(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(view_state, client, user_state))]
|
||||
pub async fn edit_member(
|
||||
view_state: SlackViewState,
|
||||
client: &SlackHyperClient,
|
||||
|
|
@ -76,6 +81,7 @@ pub async fn edit_member(
|
|||
user_id: user::Id<Trusted>,
|
||||
member_id: member::Id<Trusted>,
|
||||
) -> Result<(), Error> {
|
||||
trace!("Editing member");
|
||||
let data = member::View::try_from(view_state).change_context(Error::ParsingView)?;
|
||||
|
||||
data.update(member_id, &user_state.db)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use slack_morphism::prelude::*;
|
|||
use tracing::{debug, error};
|
||||
use trigger::{create_trigger, edit_trigger};
|
||||
|
||||
use crate::fields;
|
||||
use crate::models::system::System;
|
||||
use crate::models::{self, Trusted, user};
|
||||
|
||||
|
|
@ -20,11 +21,12 @@ pub async fn process_interaction_event(
|
|||
let client = environment.client.clone();
|
||||
let states = environment.user_state.clone();
|
||||
|
||||
if let Err(err) = interaction_event(client, event, states).await {
|
||||
error!("Error processing interaction event: {:#?}", err);
|
||||
if let Err(error) = interaction_event(client, event, states).await {
|
||||
error!(?error, "Error processing interaction event");
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(client, event, states))]
|
||||
async fn interaction_event(
|
||||
client: Arc<SlackHyperClient>,
|
||||
event: SlackInteractionEvent,
|
||||
|
|
@ -34,17 +36,19 @@ async fn interaction_event(
|
|||
SlackInteractionEvent::ViewSubmission(slack_interaction_view_submission_event) => {
|
||||
match slack_interaction_view_submission_event.view.view {
|
||||
SlackView::Home(view) => {
|
||||
debug!("Received home view: {:#?}", view);
|
||||
debug!(?view, "Received home view");
|
||||
Ok(())
|
||||
}
|
||||
SlackView::Modal(ref view) => {
|
||||
debug!("Received modal view: {:#?}", view);
|
||||
debug!(?view, "Received modal view");
|
||||
|
||||
let user_id: user::Id<Trusted> =
|
||||
slack_interaction_view_submission_event.user.id.into();
|
||||
let states = states.read().await;
|
||||
let user_state = states.get_user_state::<user::State>().unwrap();
|
||||
|
||||
fields!(user_id = %&user_id);
|
||||
|
||||
let Some(view_state) = slack_interaction_view_submission_event
|
||||
.view
|
||||
.state_params
|
||||
|
|
@ -104,8 +108,8 @@ async fn handle_modal_view(
|
|||
.map(models::member::Id::new)
|
||||
else {
|
||||
error!(
|
||||
"Failed to parse member id from external id {}. Bailing in case this was a malicious call",
|
||||
id
|
||||
id,
|
||||
"Failed to parse member id from external id. Bailing in case this was a malicious call",
|
||||
);
|
||||
return Ok(());
|
||||
};
|
||||
|
|
@ -113,8 +117,8 @@ async fn handle_modal_view(
|
|||
let Ok(trusted_member_id) = member_id.validate_by_user(&user_id, &user_state.db).await
|
||||
else {
|
||||
error!(
|
||||
"Failed to validate member id from external id {}. Bailing in case this was a malicious call",
|
||||
id
|
||||
id,
|
||||
"Failed to validate member id from external id. Bailing in case this was a malicious call",
|
||||
);
|
||||
return Ok(());
|
||||
};
|
||||
|
|
@ -136,8 +140,8 @@ async fn handle_modal_view(
|
|||
let Ok(trusted_member_id) = member_id.validate_by_user(&user_id, &user_state.db).await
|
||||
else {
|
||||
error!(
|
||||
"Failed to validate member id from external id {}. Bailing in case this was a malicious call",
|
||||
id
|
||||
id,
|
||||
"Failed to validate member id from external id. Bailing in case this was a malicious call",
|
||||
);
|
||||
return Ok(());
|
||||
};
|
||||
|
|
@ -161,8 +165,8 @@ async fn handle_modal_view(
|
|||
.flatten()
|
||||
else {
|
||||
error!(
|
||||
"Failed to fetch system id for user id {}. Bailing in case this was a malicious call",
|
||||
user_id
|
||||
%user_id,
|
||||
"Failed to fetch system id for user id. Bailing in case this was a malicious call"
|
||||
);
|
||||
return Ok(());
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use error_stack::{Result, ResultExt, bail};
|
||||
use slack_morphism::prelude::*;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
BOT_TOKEN,
|
||||
BOT_TOKEN, fields,
|
||||
models::{
|
||||
Trusted, member,
|
||||
system::System,
|
||||
|
|
@ -21,6 +22,7 @@ pub enum Error {
|
|||
NoSystem,
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(view_state, client, user_state), fields(system_id))]
|
||||
pub async fn create_trigger(
|
||||
view_state: SlackViewState,
|
||||
client: &SlackHyperClient,
|
||||
|
|
@ -39,11 +41,16 @@ pub async fn create_trigger(
|
|||
bail!(Error::NoSystem);
|
||||
};
|
||||
|
||||
let _id = data
|
||||
fields!(system_id = %system_id);
|
||||
|
||||
let id = data
|
||||
.add(system_id, member_id, &user_state.db)
|
||||
.await
|
||||
.change_context(Error::Sqlx)?;
|
||||
|
||||
fields!(id = %id);
|
||||
debug!("Trigger created");
|
||||
|
||||
let session = client.open_session(&BOT_TOKEN);
|
||||
let user: SlackUserId = user_id.into();
|
||||
|
||||
|
|
@ -65,6 +72,7 @@ pub async fn create_trigger(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(view_state, client, user_state))]
|
||||
pub async fn edit_trigger(
|
||||
view_state: SlackViewState,
|
||||
client: &SlackHyperClient,
|
||||
|
|
|
|||
30
src/main.rs
30
src/main.rs
|
|
@ -7,24 +7,26 @@ mod events;
|
|||
mod interactions;
|
||||
mod models;
|
||||
mod oauth;
|
||||
mod util;
|
||||
|
||||
use crate::models::Trusted;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
use std::{process::ExitCode, sync::Arc};
|
||||
|
||||
use axum::{extract::MatchedPath, http::Request};
|
||||
use commands::process_command_event;
|
||||
use error_stack::{report, ResultExt};
|
||||
use error_stack::{ResultExt, report};
|
||||
use events::process_push_event;
|
||||
use interactions::process_interaction_event;
|
||||
use models::{system, user};
|
||||
use oauth::oauth_handler;
|
||||
use slack_morphism::prelude::*;
|
||||
use sqlx::sqlite::SqliteConnectOptions;
|
||||
use sqlx::SqlitePool;
|
||||
use tracing::debug;
|
||||
use tracing::{info, level_filters::LevelFilter};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
use sqlx::{SqlitePool, sqlite::SqliteConnectOptions};
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::info_span;
|
||||
use tracing::{debug, info, level_filters::LevelFilter};
|
||||
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
/// The slack app token. Used for socket mode if we ever decide to use it.
|
||||
pub static APP_TOKEN: LazyLock<SlackApiToken> =
|
||||
|
|
@ -150,6 +152,22 @@ async fn main() -> error_stack::Result<ExitCode, Error> {
|
|||
.events_layer(&signing_secret)
|
||||
.with_event_extractor(SlackEventsExtractors::interaction_event()),
|
||||
),
|
||||
)
|
||||
.layer(
|
||||
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
||||
// Log the matched route's path (with placeholders not filled in).
|
||||
// Use request.uri() or OriginalUri if you want the real path.
|
||||
let matched_path = request
|
||||
.extensions()
|
||||
.get::<MatchedPath>()
|
||||
.map(MatchedPath::as_str);
|
||||
|
||||
info_span!(
|
||||
"slack_system_bot::http_request",
|
||||
method = ?request.method(),
|
||||
matched_path,
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
info!("Slack bot is running");
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::id;
|
|||
|
||||
use super::{
|
||||
Trusted, Untrusted, system,
|
||||
trigger::{self, Trigger},
|
||||
trigger::{self, Trigger, Type},
|
||||
user,
|
||||
};
|
||||
|
||||
|
|
@ -100,8 +100,9 @@ impl Id<Trusted> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: move sql to rust struct
|
||||
// TO-DO: move SQL to rust struct
|
||||
#[derive(FromRow, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Member {
|
||||
/// The ID of the member
|
||||
pub id: Id<Trusted>,
|
||||
|
|
@ -143,7 +144,7 @@ impl Member {
|
|||
WHERE system_id = $1 AND id = $2
|
||||
"#,
|
||||
system_id,
|
||||
// safe because this query also checks if the ID is trusted
|
||||
// Safe because this query also checks if the ID is trusted
|
||||
member_id.id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
|
|
@ -179,7 +180,7 @@ impl Member {
|
|||
}
|
||||
}
|
||||
|
||||
/// all information required to display a member
|
||||
/// All information required to display a member
|
||||
#[derive(FromRow, Debug)]
|
||||
pub struct TriggeredMember {
|
||||
/// The ID of the member
|
||||
|
|
@ -190,8 +191,8 @@ pub struct TriggeredMember {
|
|||
pub profile_picture_url: Option<String>,
|
||||
/// The trigger text that was matched
|
||||
pub trigger_text: String,
|
||||
/// Whether this was a prefix trigger (true) or suffix trigger (false)
|
||||
pub is_prefix: bool,
|
||||
/// The type of trigger
|
||||
pub typ: Type,
|
||||
}
|
||||
|
||||
impl From<Member> for TriggeredMember {
|
||||
|
|
@ -201,7 +202,7 @@ impl From<Member> for TriggeredMember {
|
|||
display_name: value.display_name,
|
||||
profile_picture_url: value.profile_picture_url,
|
||||
trigger_text: String::new(),
|
||||
is_prefix: true,
|
||||
typ: Type::Prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,22 +14,8 @@ id!(
|
|||
=> Message
|
||||
);
|
||||
|
||||
impl Id<Trusted> {
|
||||
pub async fn delete(self, db_pool: &SqlitePool) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM message_logs
|
||||
WHERE id = $1
|
||||
"#,
|
||||
self.id
|
||||
)
|
||||
.execute(db_pool)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct MessageLog {
|
||||
pub id: Id<Trusted>,
|
||||
pub member_id: member::Id<Trusted>,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
pub mod member;
|
||||
pub mod message;
|
||||
pub mod system;
|
||||
pub mod trigger;
|
||||
pub mod user;
|
||||
|
||||
pub trait Trustability: Send + Sync {}
|
||||
pub trait Trustability: Send + Sync + Debug {}
|
||||
|
||||
/// A trusted/valid ID
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
|
|||
|
|
@ -25,6 +25,18 @@ impl Id<Trusted> {
|
|||
pub async fn list_triggers(self, db: &SqlitePool) -> Result<Vec<Trigger>, sqlx::Error> {
|
||||
Trigger::fetch_by_system_id(db, self).await
|
||||
}
|
||||
|
||||
pub async fn rename(self, new_name: &str, db: &SqlitePool) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE systems SET name = ? WHERE id = ?",
|
||||
new_name,
|
||||
self.id
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, FromRow, PartialEq, Eq, Clone)]
|
||||
|
|
@ -44,6 +56,7 @@ impl From<String> for SlackOauthToken {
|
|||
}
|
||||
|
||||
#[derive(FromRow, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct System {
|
||||
#[sqlx(flatten)]
|
||||
pub id: Id<Trusted>,
|
||||
|
|
@ -65,6 +78,7 @@ pub enum ChangeActiveMemberError {
|
|||
}
|
||||
|
||||
impl System {
|
||||
#[tracing::instrument(skip(db))]
|
||||
pub async fn fetch_by_user_id<T>(
|
||||
db: &SqlitePool,
|
||||
user_id: &user::Id<T>,
|
||||
|
|
@ -175,14 +189,15 @@ impl System {
|
|||
display_name,
|
||||
profile_picture_url,
|
||||
triggers.text as trigger_text,
|
||||
triggers.is_prefix
|
||||
triggers.typ
|
||||
FROM
|
||||
members
|
||||
JOIN
|
||||
triggers ON members.id = triggers.member_id
|
||||
WHERE
|
||||
(triggers.is_prefix = TRUE AND ?1 LIKE triggers.text || '%') OR
|
||||
(triggers.is_prefix = FALSE AND ?1 LIKE '%' || triggers.text)
|
||||
-- See trigger.rs file for all types and names
|
||||
(triggers.typ = 0 AND ?1 LIKE triggers.text || '%') OR
|
||||
(triggers.typ = 1 AND ?1 LIKE '%' || triggers.text)
|
||||
"#,
|
||||
message
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::id;
|
||||
|
||||
use super::{Trustability, Trusted, Untrusted, member, system};
|
||||
|
|
@ -69,13 +71,51 @@ pub enum Error {
|
|||
Sqlx,
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::Type, displaydoc::Display, PartialEq, Eq)]
|
||||
#[repr(i64)]
|
||||
pub enum Type {
|
||||
/// Suffix
|
||||
Suffix = 0,
|
||||
/// Prefix
|
||||
Prefix = 1,
|
||||
}
|
||||
|
||||
impl From<i64> for Type {
|
||||
fn from(value: i64) -> Self {
|
||||
match value {
|
||||
0 => Self::Suffix,
|
||||
1 => Self::Prefix,
|
||||
_ => unreachable!(
|
||||
"Invalid type value. This means the database and rust struct are out of sync"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, displaydoc::Display)]
|
||||
/// Unknown type
|
||||
pub struct UnknownType(String);
|
||||
|
||||
impl FromStr for Type {
|
||||
type Err = UnknownType;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"suffix" => Ok(Self::Suffix),
|
||||
"prefix" => Ok(Self::Prefix),
|
||||
_ => Err(UnknownType(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Trigger {
|
||||
pub id: Id<Trusted>,
|
||||
pub member_id: member::Id<Trusted>,
|
||||
pub system_id: system::Id<Trusted>,
|
||||
pub text: String,
|
||||
pub is_prefix: bool,
|
||||
pub typ: Type,
|
||||
}
|
||||
|
||||
impl Trigger {
|
||||
|
|
@ -91,7 +131,7 @@ impl Trigger {
|
|||
member_id as "member_id: member::Id<Trusted>",
|
||||
system_id as "system_id: system::Id<Trusted>",
|
||||
text,
|
||||
is_prefix
|
||||
typ
|
||||
FROM
|
||||
triggers
|
||||
WHERE id = $1
|
||||
|
|
@ -114,7 +154,7 @@ impl Trigger {
|
|||
member_id as "member_id: member::Id<Trusted>",
|
||||
system_id as "system_id: system::Id<Trusted>",
|
||||
text,
|
||||
is_prefix
|
||||
typ
|
||||
FROM
|
||||
triggers
|
||||
WHERE
|
||||
|
|
@ -138,7 +178,7 @@ impl Trigger {
|
|||
member_id as "member_id: member::Id<Trusted>",
|
||||
system_id as "system_id: system::Id<Trusted>",
|
||||
text,
|
||||
is_prefix
|
||||
typ
|
||||
FROM
|
||||
triggers
|
||||
WHERE member_id = $1
|
||||
|
|
@ -154,24 +194,28 @@ impl Trigger {
|
|||
#[derive(Debug)]
|
||||
pub struct View {
|
||||
pub text: String,
|
||||
pub is_prefix: bool,
|
||||
pub typ: Type,
|
||||
}
|
||||
|
||||
impl Default for View {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: String::new(),
|
||||
is_prefix: true,
|
||||
typ: Type::Prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl View {
|
||||
pub fn create_blocks(self) -> Vec<SlackBlock> {
|
||||
let prefix_choice =
|
||||
SlackBlockChoiceItem::new(SlackBlockText::Plain("prefix".into()), "prefix".into());
|
||||
let suffix_choice =
|
||||
SlackBlockChoiceItem::new(SlackBlockText::Plain("suffix".into()), "suffix".into());
|
||||
let prefix_choice = SlackBlockChoiceItem::new(
|
||||
SlackBlockText::Plain(Type::Prefix.to_string().into()),
|
||||
Type::Prefix.to_string(),
|
||||
);
|
||||
let suffix_choice = SlackBlockChoiceItem::new(
|
||||
SlackBlockText::Plain(Type::Suffix.to_string().into()),
|
||||
Type::Suffix.to_string(),
|
||||
);
|
||||
|
||||
slack_blocks!(
|
||||
some_into(
|
||||
|
|
@ -191,14 +235,13 @@ impl View {
|
|||
SlackInputBlock::new(
|
||||
"Trigger Type".into(),
|
||||
SlackBlockRadioButtonsElement::new(
|
||||
"is_prefix".into(),
|
||||
vec![prefix_choice.clone(), suffix_choice.clone()]
|
||||
"type".into(),
|
||||
vec![prefix_choice, suffix_choice]
|
||||
)
|
||||
.with_initial_option(if self.is_prefix {
|
||||
prefix_choice
|
||||
} else {
|
||||
suffix_choice
|
||||
})
|
||||
.with_initial_option(SlackBlockChoiceItem::new(
|
||||
SlackBlockText::Plain(Type::Prefix.to_string().into()),
|
||||
Type::Prefix.to_string(),
|
||||
))
|
||||
.into(),
|
||||
)
|
||||
.with_optional(false)
|
||||
|
|
@ -222,14 +265,14 @@ impl View {
|
|||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO triggers (system_id, member_id, text, is_prefix)
|
||||
INSERT INTO triggers (system_id, member_id, text, typ)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id
|
||||
"#,
|
||||
system_id.id,
|
||||
member_id.id,
|
||||
self.text,
|
||||
self.is_prefix
|
||||
self.typ
|
||||
)
|
||||
.fetch_one(db_pool)
|
||||
.await
|
||||
|
|
@ -250,11 +293,11 @@ impl View {
|
|||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE triggers
|
||||
SET text = $1, is_prefix = $2
|
||||
SET text = $1, typ = $2
|
||||
WHERE id = $3
|
||||
"#,
|
||||
self.text,
|
||||
self.is_prefix,
|
||||
self.typ,
|
||||
trigger_id.id,
|
||||
)
|
||||
.execute(db)
|
||||
|
|
@ -264,13 +307,6 @@ impl View {
|
|||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub const fn new(trigger_text: String, is_prefix: bool) -> Self {
|
||||
Self {
|
||||
text: trigger_text,
|
||||
is_prefix,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_add_view(self, member_id: member::Id<Trusted>) -> SlackView {
|
||||
SlackView::Modal(
|
||||
SlackModalView::new("Add a new trigger".into(), self.create_blocks())
|
||||
|
|
@ -299,9 +335,12 @@ impl From<SlackViewState> for View {
|
|||
view.text = text;
|
||||
}
|
||||
}
|
||||
"is_prefix" => {
|
||||
"typ" => {
|
||||
if let Some(option) = content.selected_option {
|
||||
view.is_prefix = option.value == "prefix";
|
||||
match option.value.parse::<Type>() {
|
||||
Ok(typ) => view.typ = typ,
|
||||
Err(error) => warn!(?error, "Error parsing trigger type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
|
|
@ -319,7 +358,7 @@ impl From<Trigger> for View {
|
|||
fn from(trigger: Trigger) -> Self {
|
||||
Self {
|
||||
text: trigger.text,
|
||||
is_prefix: trigger.is_prefix,
|
||||
typ: trigger.typ,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, ret)]
|
||||
pub async fn oauth_handler(
|
||||
Query(code): Query<OauthCode>,
|
||||
State(state): State<user::State>,
|
||||
|
|
|
|||
51
src/util.rs
Normal file
51
src/util.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/// Records one or more fields in the current span.
|
||||
///
|
||||
/// Use % for recording a field with a [`Display`] value.
|
||||
///
|
||||
/// Use ? for recording a field with a [`Debug`] value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rs
|
||||
/// fields!(user_name = user.name, system_id = %system.id, member_id = ?member.id);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! fields {
|
||||
// recursive cases
|
||||
($name:tt = %$value:expr, $($rest:tt)?) => {
|
||||
::tracing::span::Span::current()
|
||||
.record(::std::stringify!($name), ::tracing::field::display($value));
|
||||
|
||||
fields!($($rest)+);
|
||||
};
|
||||
|
||||
($name:tt = ?$value:expr, $($rest:tt)?) => {
|
||||
::tracing::span::Span::current()
|
||||
.record(::std::stringify!($name), ::tracing::field::debug($value));
|
||||
|
||||
fields!($($rest)+);
|
||||
};
|
||||
|
||||
($name:tt = $value:expr, $($rest:tt)?) => {
|
||||
::tracing::span::Span::current()
|
||||
.record(::std::stringify!($name), $value);
|
||||
|
||||
fields!($($rest)+);
|
||||
};
|
||||
|
||||
// base cases
|
||||
($name:tt = %$value:expr) => {
|
||||
::tracing::span::Span::current()
|
||||
.record(::std::stringify!($name), ::tracing::field::display($value));
|
||||
};
|
||||
|
||||
($name:tt = ?$value:expr) => {
|
||||
::tracing::span::Span::current()
|
||||
.record(::std::stringify!($name), ::tracing::field::debug($value));
|
||||
};
|
||||
|
||||
($name:tt = $value:expr) => {
|
||||
::tracing::span::Span::current()
|
||||
.record(::std::stringify!($name), $value);
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue