feat: add bot_id to system_automations and enhance schedule handling

- Introduced `bot_id` column in `system_automations` table (migration 6.0.0.sql) and updated the Diesel schema/model to include it.
- Adjusted migrations to remove the hard‑coded “Update Summary” automation and only create an index on the `name` column.
- Extended the `SET_SCHEDULE` keyword:
  - Added a second string argument for the script name.
  - Passed the invoking user's `bot_id` to the database layer.
  - Updated function signature to accept a full `UserSession` instead of discarding it.
- Modified `execute_set_schedule` to store `bot_id`, script name, and activation flag; added conflict handling on `(bot_id, param)` to update schedule and reset trigger state.
- Updated imports and logging to reflect new parameters.

These changes enable per‑bot automation management, allow specifying the script to run, and improve idempotent schedule updates.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-11-02 19:32:25 -03:00
parent 0f842adf69
commit d2ee695d8b
6 changed files with 41 additions and 28 deletions

View file

@ -55,6 +55,7 @@ CREATE INDEX idx_organizations_slug ON public.organizations USING btree (slug);
CREATE TABLE public.system_automations ( CREATE TABLE public.system_automations (
id uuid DEFAULT gen_random_uuid() NOT NULL, id uuid DEFAULT gen_random_uuid() NOT NULL,
bot_id uuid NOT NULL,
kind int4 NOT NULL, kind int4 NOT NULL,
"target" varchar(32) NULL, "target" varchar(32) NULL,
schedule bpchar(12) NULL, schedule bpchar(12) NULL,

View file

@ -6,20 +6,5 @@
-- Add name column to system_automations if it doesn't exist -- Add name column to system_automations if it doesn't exist
ALTER TABLE public.system_automations ADD COLUMN IF NOT EXISTS name VARCHAR(255); ALTER TABLE public.system_automations ADD COLUMN IF NOT EXISTS name VARCHAR(255);
-- Insert update-summary automation (runs every minute)
-- kind = 3 (Scheduled trigger)
-- schedule format: minute hour day month weekday
-- "* * * * *" = every minute
INSERT INTO public.system_automations (name, kind, target, param, schedule, is_active)
VALUES (
'Update Summary',
0,
NULL,
'update-summary.bas',
'* * * * *',
true
)
ON CONFLICT DO NOTHING;
-- Create index on name column for faster lookups -- Create index on name column for faster lookups
CREATE INDEX IF NOT EXISTS idx_system_automations_name ON public.system_automations(name); CREATE INDEX IF NOT EXISTS idx_system_automations_name ON public.system_automations(name);

11
migrations/6.0.9.sql Normal file
View file

@ -0,0 +1,11 @@
-- Migration 6.0.9: Add bot_id column to system_automations
-- Description: Introduces a bot_id column to associate automations with a specific bot.
-- The column is added as UUID and indexed for efficient queries.
-- Add bot_id column if it does not exist
ALTER TABLE public.system_automations
ADD COLUMN IF NOT EXISTS bot_id UUID NOT NULL;
-- Create an index on bot_id for faster lookups
CREATE INDEX IF NOT EXISTS idx_system_automations_bot_id
ON public.system_automations (bot_id);

View file

@ -3,22 +3,23 @@ use log::info;
use rhai::Dynamic; use rhai::Dynamic;
use rhai::Engine; use rhai::Engine;
use serde_json::{json, Value}; use serde_json::{json, Value};
use uuid::Uuid;
use crate::shared::models::TriggerKind; use crate::shared::models::TriggerKind;
use crate::shared::models::UserSession; use crate::shared::models::UserSession;
use crate::shared::state::AppState; use crate::shared::state::AppState;
pub fn set_schedule_keyword(state: &AppState, _user: UserSession, engine: &mut Engine) { pub fn set_schedule_keyword(state: &AppState, user: UserSession, engine: &mut Engine) {
let state_clone = state.clone(); let state_clone = state.clone();
engine engine
.register_custom_syntax(&["SET_SCHEDULE", "$string$"], true, { .register_custom_syntax(&["SET_SCHEDULE", "$string$", "$string$"], true, {
move |context, inputs| { move |context, inputs| {
let cron = context.eval_expression_tree(&inputs[0])?.to_string(); let cron = context.eval_expression_tree(&inputs[0])?.to_string();
let param = format!("cron_{}.rhai", cron.replace(' ', "_")); let script_name = context.eval_expression_tree(&inputs[1])?.to_string();
let mut conn = state_clone.conn.lock().unwrap(); let mut conn = state_clone.conn.lock().unwrap();
let result = execute_set_schedule(&mut *conn, &cron, &param) let result = execute_set_schedule(&mut *conn, &cron, &script_name, user.bot_id)
.map_err(|e| format!("DB error: {}", e))?; .map_err(|e| format!("DB error: {}", e))?;
if let Some(rows_affected) = result.get("rows_affected") { if let Some(rows_affected) = result.get("rows_affected") {
@ -34,29 +35,40 @@ pub fn set_schedule_keyword(state: &AppState, _user: UserSession, engine: &mut E
pub fn execute_set_schedule( pub fn execute_set_schedule(
conn: &mut diesel::PgConnection, conn: &mut diesel::PgConnection,
cron: &str, cron: &str,
param: &str, script_name: &str,
bot_uuid: Uuid,
) -> Result<Value, Box<dyn std::error::Error>> { ) -> Result<Value, Box<dyn std::error::Error>> {
info!( info!(
"Starting execute_set_schedule with cron: {}, param: {}", "Starting execute_set_schedule with cron: {}, script: {}, bot_id: {:?}",
cron, param cron, script_name, bot_uuid
); );
use crate::shared::models::system_automations; use crate::shared::models::system_automations::dsl::*;
let new_automation = ( let new_automation = (
system_automations::kind.eq(TriggerKind::Scheduled as i32), bot_id.eq(bot_uuid),
system_automations::schedule.eq(cron), kind.eq(TriggerKind::Scheduled as i32),
system_automations::param.eq(param), schedule.eq(cron),
param.eq(script_name),
is_active.eq(true),
); );
let result = diesel::insert_into(system_automations::table) let result = diesel::insert_into(system_automations)
.values(&new_automation) .values(&new_automation)
.on_conflict((bot_id, param))
.do_update()
.set((
schedule.eq(cron),
is_active.eq(true),
last_triggered.eq(None::<chrono::DateTime<chrono::Utc>>),
))
.execute(&mut *conn)?; .execute(&mut *conn)?;
Ok(json!({ Ok(json!({
"command": "set_schedule", "command": "set_schedule",
"schedule": cron, "schedule": cron,
"param": param, "script": script_name,
"bot_id": bot_uuid.to_string(),
"rows_affected": result "rows_affected": result
})) }))
} }

View file

@ -65,6 +65,7 @@ impl TriggerKind {
#[diesel(table_name = system_automations)] #[diesel(table_name = system_automations)]
pub struct Automation { pub struct Automation {
pub id: Uuid, pub id: Uuid,
pub bot_id: Uuid,
pub kind: i32, pub kind: i32,
pub target: Option<String>, pub target: Option<String>,
pub schedule: Option<String>, pub schedule: Option<String>,
@ -265,6 +266,7 @@ pub mod schema {
diesel::table! { diesel::table! {
system_automations (id) { system_automations (id) {
id -> Uuid, id -> Uuid,
bot_id -> Uuid,
kind -> Int4, kind -> Int4,
target -> Nullable<Text>, target -> Nullable<Text>,
schedule -> Nullable<Text>, schedule -> Nullable<Text>,

View file

@ -1,3 +1,5 @@
SET_SCHEDULE "* * * * *"
let text = GET "announcements.gbkb/news/news.pdf" let text = GET "announcements.gbkb/news/news.pdf"
let resume = LLM "Resume this document, in a table (DO NOT THINK) no_think: " + text let resume = LLM "Resume this document, in a table (DO NOT THINK) no_think: " + text